上一节说了后台的MVC,现在开始讲重点,前端的MVC又是一个什么鬼。
很长一段时间我都没有搞清楚MVC和MVVM。
一直在说ng是MVC,react和Vue是MVVM,MVVM我用过了,用过vue和react,他们的数据绑定,那么MVC究竟是什么样子呢?
一个简单MVC的实现
有一篇很好的文章讲了MVC:
http://www.imooc.com/article/4911
重点:
MVC的基础是观察者模式;
这里还有一个问题,就是MVC为什么不是23种设计模式中的一种呢?
在网上找了很多答案:
说MVC每一个M、V、C都是一种模式,即观察者模式、策略模式、组合模式的结合;
第一步:先实现model来看看
这里我们先看Model.js
//实际上这里是一个观察者模式
function Model(value) {
this._value = typeof value === 'undefined' ? '' : value;
this._listeners = [];
}
Model.prototype.set = function (value) {
var self = this;
self._value = value;
console.log(value);
// model中的值改变时,应通知注册过的回调函数
// 按照Javascript事件处理的一般机制,我们异步地调用回调函数
// 如果觉得setTimeout影响性能,也可以采用requestAnimationFrame
setTimeout(function () {
self._listeners.forEach(function (listener) {
listener.call(self, value);
});
});
};
Model.prototype.watch = function (listener) {
// 注册监听的回调函数
console.log(listener);
this._listeners.push(listener);
};
<div id="div1"></div>
<script src="./model.js"></script>
<script>
// 逻辑代码:
(function() {
var model = new Model();
var div1 = document.getElementById('div1');
var setValue=function (value) {
div1.innerHTML=value;
}
// model.watch(function(value) {
// div1.innerHTML = value;
// });
model.watch(setValue)
model.set('hello, this is a div');
model.set('hello, this is a div2');
})();
</script>
上面代码的调用是这样的:提供了一个监听方法,Model.prototype.wacth,在实例中,监听的就是setValue这个方法,(我把原来代码里面的匿名函数注释掉了,因为这样便于理解)
监听事件列队插入 listener;
这里事实上我们并没有执行直接通过setValue(‘hello, this is a div’)执行,而是调用model的set方法执行的setValue(‘hello, this is a div’)
在model的set方法中,是遍历监听的事件。并将传给set的值,传给listener中的每一个事件。
比如我们稍微改一下:
/*view*/
<div id="div1"></div>
<div id="div2"></div>
<script src="./model.js"></script>
<script>
// 逻辑代码:
(function() {
var model = new Model();
var div1 = document.getElementById('div1');
var div2 = document.getElementById('div2');
var setValue=function (value) {
div1.innerHTML=value;
}
var setValue2=function (value) {
div2.innerHTML=value;
}
model.watch(setValue)
model.watch(setValue2)
model.set('hello, this is a div2');
})();
</script>
上面的代码model监听了两个事件,为div1和div2设值的setValue()和setValue2()
这里只调用了一次set方法,但是这两个setValue都执行了。
借助观察者模式,我们已经实现了在调用model的set方法改变其值的时候,模板也同步更新,但这样的实现却很别扭,因为我们需要手动监听model值的改变(通过watch方法)并传入一个回调函数,有没有办法让view(一个或多个dom node)和model更简单的绑定呢?
进一步,改进model,分离model和view的业务
上面的代码中view层还是有写setValue函数,不符合view的逻辑,view应该是看不到内部的操作。因此通过bind的方式实现setValue
var setValue=function (value) {
div1.innerHTML=value;
}
通过bind的方式实现,在model.js中加上
Model.prototype.bind = function (node) {
// 将watch的逻辑和通用的回调函数放到这里
this.watch(function (value) {
node.innerHTML = value;
});
};
这个时候view的写法:
就只是看到节点绑定到了model上,以及设置节点的值。
(function () {
var model = new Model();
model.bind(document.getElementById('div1'));
model.bind(document.getElementById('div2'));
model.set('this is a div');
})();
最后一步:controller
这里一直只提到了MV,并没有C,其实controller是一种看不见的方式在运作
这里是controller.js的代码:
function Controller(callback) {
var models = {};
// 找到所有有bind属性的元素
var views = document.querySelectorAll('[bind]');
// 将views处理为普通数组
views = Array.prototype.slice.call(views, 0);
views.forEach(function(view) {
var modelName = view.getAttribute('bind');
// 取出或新建该元素所绑定的model
models[modelName] = models[modelName] || new Model();
// 完成该元素和指定model的绑定
models[modelName].bind(view);
});
// 调用controller的具体逻辑,将models传入,方便业务处理
callback.call(this, models);
}
view层,实际上controller层完成了view和model的绑定。
<div id="div1" view="model1"></div>
<div id="div2" view="model2"></div>
new Controller(function (models) {
console.log(models);
var model1 = models.model1;
model1.set('this is a div');
});