2020年11月20日

响应式原理(1)

作者 rourou

组件初始化的时候,先给每一个Data属性都注册getter,setter,也就是reactive化。然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里

当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件。

响应式模型
  • Data属性通过Object.defineProperty注册getter,setter

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性。其中包含三个参数:

  • obj要定义属性的对象(数据)。
  • prop要定义或修改的属性的名称或 Symbol 。
  • descriptor要定义或修改的属性描述符。

descriptor属性描述符为对象可选键值如下:

  • configurable:该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除
  • enumerable:当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中(可以理解为使用for…in遍历是是否能访问到键)
  • Writable:设置属性是否是可写的。Writable和set、get不可同时使用
  • get:当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。
  • set:当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。
 var data = {
      name:'张三',
      age:12,
      code:[
        {name:'语文'},
        {name:'数学'}
      ]
    }

    // 简化版本
    function defineReactive(target, key, value, enumerable){
      // value 为函数内部局部作用域(闭包,解决数据访问安全问题)
      
      if(typeof value === 'object' && value!= null && !Array.isArray(value)){
        reactify(value)
      }
      
      Object.defineProperty(target, key, {
        configurable: true,//设置属性是否可以被删除,属性是否可更改
        // Writable:设置属性是否是可写的。Writable和set、get同时使用
        enumerable: !!enumerable,//是否可枚举(使用for...in遍历是是否能访问到键)
        set(newValue){
          value = newValue;
        },
        get(){
          return value;
        }
      })
    }

    function reactify(data){
      let keys = Object.keys(data);

      for(var i=0;i<keys.length;i++){
        let key = keys[i];
        let val = data[key];

        if(Array.isArray(val)){
          // 数组类型
          for(var j=0;j<val.length;j++){
            reactify(val[j]);
          }
        }else{
          // 引用类型
          defineReactive(data, key, val, true);
        }
      }
    }

    reactify(data);