MVC、MVP、MVVM 模式
MVC、MVP 和 MVVM 是三种常见的软件设计模式,它们主要用于构建用户界面和分离业务逻辑。
MVC(Model-View-Controller)
模型(Model):负责处理数据和业务逻辑。
视图(View):负责展示信息给用户。
控制器(Controller):接收用户的输入并调用模型或视图的更新方法。
在 MVC 模式中,用户操作通常由视图(View)接收,然后传递给控制器(Controller)处理。Controller 根据业务逻辑,更新模型数据(Model)。View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。
// Model:管理数据和业务逻辑,并通知观察者(视图)
class Model {
constructor() {
this.data = 0;
this.observers = []; // 存放观察者
}
// 添加观察者
addObserver(observer) {
this.observers.push(observer);
}
// 移除观察者
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
// 通知所有观察者
notifyObservers() {
this.observers.forEach(observer => observer.update(this));
}
increment() {
this.data++;
this.notifyObservers(); // 数据变化后,通知观察者
}
decrement() {
this.data--;
this.notifyObservers(); // 数据变化后,通知观察者
}
getData() {
return this.data;
}
}
class View {
constructor() {
this.display = document.getElementById('display');
this.incrementButton = document.getElementById('increment');
this.decrementButton = document.getElementById('decrement');
}
bindClick(controller) {
this.incrementButton.addEventListener('click', () => {
controller.increment();
});
this.decrementButton.addEventListener('click', () => {
controller.decrement();
});
}
// 当模型变化时,模型调用此方法,传入自身
update(model) {
this.render(model.getData());
}
render(data) {
this.display.innerText = data;
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
// 将视图注册为模型的观察者
this.model.addObserver(this.view);
// 让视图知道控制器,以便通知用户交互
this.view.bindClick(this);
// 初始化视图显示
this.view.render(this.model.getData());
}
handleIncrement() {
this.model.increment();
}
handleDecrement() {
this.model.decrement();
}
}
const app = new Controller(new Model(), new View());
MVP(Model-View-Presenter)
模型(Model):与 MVC 中的相同。
视图(View):与 MVC 中的相同,但通常包含一个接口来定义如何与 Presenter 交互。
演示者(Presenter):充当 View 与 Model 的中间人,它封装了所有与用户交互相关的代码,并将这些操作转换为对模型的请求。
在 MVP 模式中,视图不直接与模型交互,而是通过演示者来处理。这使得代码更加模块化,易于测试和维护。
class Model {
constructor() {
this.data = 0;
}
increment() {
this.data++;
}
decrement() {
this.data--;
}
getData() {
return this.data;
}
}
class View {
constructor(presenter) {
this.display = document.getElementById('display');
this.incrementButton = document.getElementById('increment');
this.decrementButton = document.getElementById('decrement');
this.incrementButton.addEventListener('click', () => {
presenter.handleIncrement();
});
this.decrementButton.addEventListener('click', () => {
presenter.handleDecrement();
});
}
render(data) {
this.display.innerText = data;
}
}
class Presenter {
constructor(model, view) {
this.model = model;
this.view = view;
}
handleIncrement() {
this.model.increment();
this.view.render(this.model.getData());
}
handleDecrement() {
this.model.decrement();
this.view.render(this.model.getData());
}
}
const app = new Presenter(new Model(), new View(app));
MVVM(Model-View-ViewModel)
模型(Model):与 MVC/MVP 中的相同。
视图(View):与 MVC/MVP 中的相同,但它通常是双向绑定的,即当数据变化时自动更新视图,反之亦然。
视图模型(ViewModel):作为连接视图和模型的桥梁,它将视图的状态映射到模型
单例模式
单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
var Singleton = function (name) {
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function () {
alert(this.name);
};
Singleton.getInstance = function (name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
};
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
alert(a === b); // true
或者:
var Singleton = function (name) {
this.name = name;
};
Singleton.prototype.getName = function () {
alert(this.name);
};
Singleton.getInstance = (function () {
var instance = null;
return function (name) {
if (!instance) {
instance = new Singleton(name);
}
return instance;
};
})();
惰性单例:合适的时候才创建对象,且只创建唯一的。
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments));
};
};
策略模式
策略模式的定义是: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
看一个例子:绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资。写一段代码,计算员工的年终奖。
将每一种计算方式定义成单独的函数
var strategies = {
S: function (salary) {
return salary * 4;
},
A: function (salary) {
return salary * 3;
},
B: function (salary) {
return salary * 2;
},
};
var calculateBonus = function (level, salary) {
return strategies[level](salary);
};
console.log(calculateBonus('S', 20000));
console.log(calculateBonus('A', 10000));
再看一个例子:用策略模式来重构表单校验。
// 定义校验规则
var strategies = {
isNonEmpty: function (value, errorMsg) {
if (!value) {
return errorMsg;
}
},
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
isMobile: function (value, errorMsg) {
if (!/^1[34578]\d{9}$/.test(value)) {
return errorMsg;
}
},
};
// 校验函数
var Validator = function () {
this.cache = [];
};
Validator.prototype.add = function (dom, rule, errorMsg) {
var ary = rule.split(':');
this.cache.push(function () {
var strategy = ary.shift();
ary.unshift(dom.value);
ary.push(errorMsg);
return strategies[strategy].apply(dom, ary);
});
};
Validator.prototype.start = function () {
for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
var msg = validatorFunc();
if (msg) {
return msg;
}
}
};
// 校验表单
var v = new Validator();
v.add(document.getElementById('username'), 'isNonEmpty', '用户名不能为空');
v.add(
document.getElementById('username'),
'minLength:3',
'用户名长度不能小于 3 位'
);
v.add(document.getElementById('phoneNumber'), 'isMobile', '手机号码格式不正确');
优缺点
优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺点:
- 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
- 必须知道所有的策略类,并自行决定使用哪一个策略类,违反最少知识原则。
代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
虚拟代理实现图片预加载:
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return function (src) {
imgNode.src = src;
};
})();
var proxyImage = (function () {
var img = new Image();
img.onload = function () {
myImage(img.src);
};
return function (src) {
myImage('loading.gif');
img.src = src;
};
})();
proxyImage('1.jpg');
虚拟代理实现 console
var miniConsole = (function () {
var cache = [];
var handler = function (ev) {
if (ev.keyCode === 113) {
var script = document.createElement('script');
script.onload = function () {
for (var i = 0, fn; (fn = cache[i++]); ) {
fn();
}
};
script.src = 'miniConsole.js';
document.getElementsByTagName('head')[0].appendChild(script);
document.body.removeEventListener('keydown', handler); // 只加载一次 miniConsole.js
}
};
document.body.addEventListener('keydown', handler, false);
return {
log: function () {
var args = arguments;
cache.push(function () {
return miniConsole.log.apply(miniConsole, args);
});
},
};
})();
miniConsole.log(11); // miniConsole.js 代码
miniConsole = {
log: function () {
console.log(Array.prototype.join.call(arguments));
},
};
为各种计算方式创建缓存代理
/**************** 计算乘积 *****************/
var mul = function () {
var a = 1;
for (var i = 0, len = arguments.length; i < len; i++) {
a *= arguments[i];
}
return a;
};
/**************** 计算加和 *****************/
var plus = function () {
var a = 0;
for (var i = 0, len = arguments.length; i < len; i++) {
a += arguments[i];
}
return a;
};