参考 Vue.js,作为一个 MVVM 框架的基本实现原理要包括:
- 数据代理
- 模板解析
- 数据绑定
本文不直接阅读 Vue 的源码,而是剖析 github 上仿 vue 实现的mvvm库,为以后阅读 Vue 源码打基础.
知识准备
DocumentFragment
引用MDN:
DocumentFragment 接口表示一个没有父级文件的最小文档对象。它被当做一个轻量版的 Document 使用,用于存储已排好版的或尚未打理好格式的 XML 片段。最大的区别是因为 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会引起 DOM 树的重新渲染的操作(reflow) ,且不会导致性能等问题。
这个接口用来创建一个文档对象,可以联想到 Vue 的虚拟 DOM,MVVM 用它来复制目标 DOM 到内存中,所有的 DOM 操作尽量在此修改,再插入到真实 DOM 中,从而尽量减少 DOM 的修改,提高性能.
数据代理
概念
通过一个对象代理对另一个对象中属性的操作(读/写)
在 MVVM 中就是通过 vm(实例)对象的_data 属性来代理 data 对象中所有属性的操作,读 data 的属性读的是 vm._data 的属性,写的是 vm._data 的属性,然后内部去检测_data 的变化,而不是 data.
好处: 更方便的操作 data 中的数据
源码
发生在 new MVVM 时,来看看源码的带注释版,就在 mvvm.js 文件,不过是带删减版,只留下实现数据代理的代码:
function MVVM(options) {
// 将选项对象保存到vm
this.$options = options;
// 将data对象保存到vm和data变量中
var data = (this._data = this.$options.data);
//将实例vm保存在me变量中
var me = this;
// 遍历data中所有属性
Object.keys(data).forEach(function(key) {
// 对指定属性实现代理
me._proxy(key);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
实例的方法_proxy()如下
MVVM.prototype = {
// 对指定属性实现代理
_proxy: function(key) {
// 保存vm
var me = this;
// 给vm添加指定属性名的属性(使用属性描述)
Object.defineProperty(me, key, {
configurable: false, // 不能再重新定义
enumerable: true, // 可以枚举
// 当通过vm.name读取属性值时自动调用
get: function proxyGetter() {
// 读取data中对应属性值返回(实现代理读操作)
return me._data[key];
},
// 当通过vm.name = 'xxx'时自动调用
set: function proxySetter(newVal) {
// 将最新的值保存到data中对应的属性上(实现代理写操作)
me._data[key] = newVal;
}
});
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
基本实现流程
- 在 new MVVM 的时候,首先把传进来的选项对象保存到实例的$option 属性中;
- 然后把 vm.$option.data 保存到实例的_data 属性中;
- 遍历_data 中的属性,通过 Object.defineProperty()给 vm 添加与 data 对象的属性相同的属性描述符;
- 其中所有添加的属性都包含 getter/setter,把对当前属性的读和写都重定向到 vm._data 的对应属性中,这样 this.xxx 其实读写的是 this._data.xxx;