Vue2.x源码阅读 - 响应式

85 阅读3分钟

observe方法

  • 这个方法是响应式处理的一个入口 它对所以非VNode类型的对象进行一个响应式的处理

    observe方法的核心

    
    function observe(value): Observer | void {
        if (!isObject(value) || value instanceof VNode) {
            return;
        }
        if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
            // 该数据已经进行过响应式处理了
            return value.__ob__;
        } else {
            // 实例化一个Observer对象 在其中对于value进行响应式处理
            ob = new Observer(value);
        }
        return ob;
    }
    

Observer

  • 实例化的时候接收一个值作为参数, 对于数组和普通对象进行不同的处理

    数组无法通过Object.defineProperty去进行数据劫持, 通过修改该数组的原型对象来实现数组操作的响应式

    // 所有的数组对象指向同一个公共的方法
    const arrayProto = Array.prototype
    // arrayMethods是一个原型对象为Array的原型对象 但是进行了处理
    const arrayMethods = Object.create(arrayProto)
    // 以下七种方法就是可以触发响应式(重新渲染界面)的方法
    const methodsToPatch = ['push','pop','shift','unshift','splice', 'sort','reverse']
    // 覆盖arrayMethods中的方法
    methodsToPatch.forEach(function(method) {
        const original = arrayMethods[method];
        Object.defineProperty(arrayMethods, method, {
             value: function(...args) {
                 // 保存初始方法的执行结果
                 const result = original(...args)
                 // __ob__ 属性上保存的就是该对象的响应式对象
                 const ob = this.__ob__
                 let inserted
                 // args是一个参数数组 对于所有需要新加入值的方法 必须对新加入的值进行一个响应式处理
                 switch(method) {
                    case 'push': case 'unshift':
                        inserted = args
                        break
                    case 'splice':
                        inserted = args.slice(2)
                        break
                 }
                // inserted是一个数组 对该数组中的数据进行响应式处理
                if (inserted) ob.observeArray(inserted)
                // 派发更新 让所有依赖于该数据的watcher触发更新
                ob.dep.notify()
                return result
             },
             enumerable: false,
             writable: true,
             configurable: true
        })
    })
    
    function observeArray(items) {
        for (let i = 0 ; i < items.length; i++) {
            observe(items[i]);
        }
    }
    

    对于数组对象是通过替换原型对象来实现方法的一个拦截。 解决一个兼容性的问题

    // 有些浏览器是支持__proto__的 有些是不支持的
    const hasProto = '__proto__' in {}
    const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
    
    // 支持__proto__属性 直接修改执行即可
    function protoAugment (target, src: Object) {
        target.__proto__ = src
    }
    
    // 不支持__proto__属性, 将公共方法上的属性拷贝到对象上
    function copyAugment (target: Object, src: Object, keys: Array<string>) {
        for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i]
         def(target, key, src[key])
        }
    }
    
    

    对于一个非数组对象来说 就只需要遍历对象的key 对每一个属性进行一个响应式处理

    
    function walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0 ; i < keys.length ; i++) {
            // 响应式的核心
            defineReactive(obj, keys[i]);
        }
    }
    
    

    Observer的核心代码

    
    class Observer {
        value: any;
        // 与数据绑定的依赖对象
        dep: Dep;
        constructor(value) {
            this.value = value
            dep = new Dep()
            // 将Observer绑定到value的__ob__属性上
            def(value, '__ob__', this)
    
            if (Array.isArray(value)) {
                 if (hasProto) {
                    protoAugment(value, arrayMethods)
                 } else {
                    copyAugment(value, arrayMethods, arrayKeys)
                }
    
                observeArray(value)
            } else {
                walk(value)
            }
        }
    }
    
    

defineReactive方法

  • 响应式处理的核心: 主要功能就是通过Object.defineProperty() 方法去对数据的访问和赋值进行一个拦截

  • defineReactive方法执行之后, 属性的getter和setter是一个Function 其中使用到了defineReactive中的数据, 所以val和childOb等都是**存在闭包Closure (defineReactive)**当中的

    defineReactive的核心代码

    
    function defineReactive(obj, key, val) {
        // dep对应该数据
        const dep = new Dep()
    
        const property = Object.getOwnPropertyDescriptor(obj, key)
    
        if (property && property.configurable) {
            // 该属性无法配置 
            return;
        }
    
        // 接下来就要重写属性的getter和setter了 所以提前缓存原来的
        const getter = property && property.get;
        const setter = property && property.set;
    
        if ((!getter || setter) && arguments.length === 2) {
            // 如果没有传value值 给val赋一个初值
            val = obj[key];
        }
        // 如果该属性的值 是一个对象的话 也对其进行响应式处理
        let childOb = observe(val);
    
        Object.defineProperty(obj, key , {
          enumerable: true,
          configurable: true,
          get: function reactiveGetter() {
              const value = getter? getter.call(obj) : val; 
              // 此处先不考虑依赖收集
    
              return value;
          },
          set: function reactiveSetter(newVal) {
              const value = getter? getter.call(obj) : val; 
              if (newVal === value || (newVal !== newVal && value !== value)) {
                  // 新值和原来的值相同的话不处理 
                  // newVal !== newVal && value !== value 这个判断是源码当中存在的 个人理解为NaN的一个判断
                  return;
              }
              if (getter && !setter) {
                  // 只读属性
                  return;
              }
              
              if (setter) {
                  val = setter.call(obj, newVal)
              } else {
                  val = newVal
              }
              // 新的值是对象时的响应式处理
              childOb = observe(val)
              // 此处先不考虑派发更新
          }
        })
    
    }