实现一个简易双向数据绑定

158 阅读2分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

1.[].slice.call(lis): 将伪数组转换为真数组

2.node.nodeType: 得到节点类型

3.Object.defineProperty(obj, propertyName, {}): 给对象添加/修改属性(指定描述符)

configurable: true/false  是否可以重新define
enumerable: true/false 是否可以枚举(for..in / keys())
value: 指定初始值
writable: true/false value是否可以修改存取(访问)描述符
get: 函数, 用来得到当前属性值
set: 函数, 用来监视当前属性值的变化
    

4.Object.keys(obj): 得到对象自身可枚举的属性名的数组

5.DocumentFragment: 文档碎片(高效批量更新多个节点)

6.obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性

数据代理(MVVM.js)

1.通过一个对象代理对另一个对象中属性的操作(读/写)

2.通过vm对象来代理data对象中所有属性的操作

3.好处: 更方便的操作data中的数据

4.基本实现流程

1). 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符
2). 所有添加的属性都包含getter/setter
3). 在getter/setter内部去操作data中对应的属性数据

模板解析(compile.js)

1.模板解析的关键对象: compile对象

2.模板解析的基本流程:

 1). 将el的所有子节点取出, 添加到一个新建的文档fragment对象中
 2). 对fragment中的所有层次子节点递归进行编译解析处理
	-  对表达式文本节点进行解析
	-  对元素节点的指令属性进行解析
    	-  事件指令解析
    	-  一般指令解析
 3). 将解析后的fragment添加到el中显示
    

3.解析表达式文本节点:

textNode.textContent = value

1). 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1
2). 从data中取出表达式对应的属性值
3). 将属性值设置为文本节点的textContent
    

4.事件指令解析: elementNode.addEventListener(事件名, 回调函数.bind(vm))

v-on:click="test"
1). 从指令名中取出事件名
2). 根据指令的值(表达式)从methods中得到对应的事件处理函数对象
3). 给当前元素节点绑定指定事件名和回调函数的dom事件监听
4). 指令解析完后, 移除此指令属性
    

5.一般指令解析: elementNode.xxx = value

1). 得到指令名和指令值(表达式)
2). 从data中根据表达式得到对应的值
3). 根据指令名确定需要操作元素节点的什么属性
    * v-text---textContent属性
    * v-html---innerHTML属性
    * v-class--className属性
4). 将得到的表达式的值设置到对应的属性上
5). 移除元素的指令属性
    
/*
相关于Vue的构造函数
 */
function MVVM(options) {
  // 将选项对象保存到vm
  this.$options = options;
  // 将data对象保存到vm和datq变量中
  var data = this._data = this.$options.data;
  //将vm保存在me变量中
  var me = this;
  // 遍历data中所有属性
  Object.keys(data).forEach(function (key) { // 属性名: name
    // 对指定属性实现代理
    me._proxy(key);
  });

  // 对data进行监视
  observe(data, this);

  // 创建一个用来编译模板的compile对象
  this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
  $watch: function (key, cb, options) {
    new Watcher(this, key, cb);
  },

  // 对指定属性实现代理
  _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;
      }
    });
  }
};