author: 陈家宾
email: 617822642@qq.com
date: 2018/3/1
MVVM 背景
都说懒惰使人进步,MVVM 的进化史,正印证了这句话,是一步步让开发人员更懒惰更简单的历史:
直接 DOM 操作 -> MVC -> MVP -> MVVM
最开始的前端交互,是很直接的 DOM 操作,最出名的这类库当数 jQuery 了,封装了 DOM API,让一切 DOM 操作都变得简单。
但当页面数据和交互多的时候,散乱的代码将使项目变得难以维护,让人发狂。所以才有了 MV* 模式的发展。
MV* 模式
MVC & MVP & MVVM 三者对比伪代码:点我
MVC
- Model:数据模型 & 手动渲染模板
- View:模板
- Controller:修改数据
MVP
- Model:数据模型
- View:模板
- Presenter:修改数据 & 手动渲染模板
MVP 是 MVC 模式的一种改造(这里不说改进,是因为两者其实很相似,没有本质上的变化),将 手动渲染 步骤** 从 Model 移到了 Presenter,让 View 和 Model 更独立更存粹了,但从另一个角度来说,也加大了 Presenter 的工作。
MVVM
- Model:数据模型
- View:带特殊属性的 html 模板
- ViewModel:依靠 Directive,修改数据 & 自动渲染
MVVM 模式依靠 Directive,实现了 模板自动渲染,极大地解放了开发者的双手,此时开发者只需关注 View 和 Model,效率和可维护性方面达到了飞跃式的进步。
下面将着重介绍下神奇的 Directive。
数据变更检测方案(Directive)
(一)手动触发绑定
在页面需要改变时,手动触发检测,改变 model 数据,并扫描元素,对有特殊标记的元素进行修改
let data = {
value: 'hello'
};
let directive = {
html: function (html) {
this.innerHTML = html;
},
value: function (html) {
this.setAttribute('value', value);
}
};
ViewModelSet('value', 'hello world');
function ViewModelSet(key, value) {
data[key] = value;
scan();
}
function scan() {
for (let elem of elems) {
elem.directive = [];
for (let attr of elem.attributes) {
if (attr.nodeName.indexOf('v-') >= 0) {
directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
}
}
}
}
(二)脏检测机制
针对手动绑定进行优化,只对修改到的数据进行更新元素
function scan(elems, val) {
let list = document.querySelectorAll(`[v-bind=${val}]`); // 只扫描修改到的数据涉及的元素
for (let elem of elems) {
for (let attr of elem.attributes) {
let dataKey = elem.getAttribute('v-bind');
if (elem.directive[attr.nodeValue] !== data[dataKey]) { // 当元素值有变时,更新元素
directive[attr.nodeValue].call(elem, data[dataKey]);
elem.directive[attr.nodeValue] = data[dataKey]; // 保存元素当前值
}
}
}
}
(三)前端数据对象劫持(Hijacking)
在上面的基础更进一步,使用 Object.defineProperty 对数据进行 get & set 监听,当数据有变时,自动执行 scan 扫描并更新元素。
原来是在改变数据时,还要手动 scan。现在只需要直接改变数据,会自动 scan,更新元素。
defineGetAndSet(data, 'value');
data.value = 'hello world';
function defineGetAndSet(obj, propName) {
Object.efineProperty(obj, propName, {
get: function () {
return this.bVal;
},
set: function (newVal) {
this.bVal = newVal;
scan();
},
enumerable: true,
configurable: true
});
}
(四)ECMAScript 6 Proxy
与方法三类似,换了种写法,这里应用了 ES6 里的 Proxy
let data = new Proxy({
get: function (obj, key) {
return obj[key];
},
set: function (obj, key, val) {
obj[key] = val;
scan();
return obj[key];
}
});
以上。
参考资料
- 《现代前端 技术解析》,张成文,2017 年 4 月第 1 版d
- 《MVC,MVP 和 MVVM 的图示》,阮一峰,2015年2月 1日,http://www.ruanyifeng.com/blo...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。