参考 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

实例的方法_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

基本实现流程

  1. 在 new MVVM 的时候,首先把传进来的选项对象保存到实例的$option 属性中;
  2. 然后把 vm.$option.data 保存到实例的_data 属性中;
  3. 遍历_data 中的属性,通过 Object.defineProperty()给 vm 添加与 data 对象的属性相同的属性描述符;
  4. 其中所有添加的属性都包含 getter/setter,把对当前属性的读和写都重定向到 vm._data 的对应属性中,这样 this.xxx 其实读写的是 this._data.xxx;

TOC