Vue2.0变化侦测Array类型的处理

688 阅读3分钟

为什么不能使用Object.defineProperty侦测Array

存取描述符getter/setter只能侦测数据的修改和读取,是无法侦测数据的增加与删除的。Object的增删也是依靠set()、delete()才能完成增删与响应(可以看我的Vue全局API里有)

那该如何追踪Array变化

了解过Vue的会知道数组的变化必须使用Array的原生函数pushpopunshiftshiftsplitsplice

这些原生函数在数组的原型链上,在Array.prototype中

this.list.push(1)  //向数组尾部添加元素
this.list.pop()    //删除数组尾部元素
...

在ES6之前没有源编程能力,无法拦截原型方法的能力。(ES6的Proxy有元编程能力)

所以为了拦截原型方法,实现拦截器覆盖Array.prototype。

拦截器

拦截器原理

const arrProto=Array.prototype
cosnt arrayMethods=Object.create(arrProto)
这样arrayMethods的__proto__就是Array.prototype,而arrayMethods属性是空的{}一个对象
Obsrver{
    constructor(value){
        if(Array.isArray(value)){
            value.__proto__ = arrayMethods
        }
    }
}

data(){
    return{
        list:[]
    }
}
当变量是数组时修改它的原型链
原本list的原型链是list.__proto__ = Array.prototype = Object.prototype = null
修改后list的原型链是list.__proto__= {} = Array.prototype = Object.prototype = null
我们就可以在这里实现拦截

拦截器实现

const arrProto=Array.prototype
cosnt arrayMethods=Object.create(arrProto)
['push','pop','shift','unshift','splice','sort','reverse']
.forEach(function(method){
  //获取到原生方法
  const origin = arrProto[method]
  Object.defineProperty(arrayMethods,method,{
    value :function(...args){
        return origin.apply(this,args)
    },
    enumerable:false,//不可枚举
    writable:true,
    configurable:true
  })
})
好了这样就实现了拦截

拦截器的兼容

有的浏览器不支持访问原型__proto__,Vue简单粗暴直接设置到被侦测的数组本身身上。

//是否支持访问原型
hasProto = '__proto__' in {}

如何收集依赖

function defineReactive (data, key, val){
    if(typeof val === "object") new Observer
    let dep = new Dep()
    Object.defineProperty(data,key,{
      enumerable:true,
      configurable:true,
      get : function(){
          dep.depend()//这里收集依赖
          return val
      },
      set : function(newVal){
          if(val === newVal){
              return
          }
          
          dep.notify()
          val = newVal 
      }
    })
}

每当你读取这个数组时都会触发get的依赖收集

Array在get中收集依赖,在拦截器中触发依赖

依赖保存在哪里

这个依赖要保存在get和拦截器都能访问的地方

那就是Observer(将变量从数据描述符变为存取描述符的函数)中,并且这个Observer保存于值Value的不可枚举_ob_中,你可以打开Vue看一下。

def(value,'_ob_',this)
fuction def(obj,key,val,enumerable){
    Object.defineProperty(obj,key,{
      value:val,
      enumerable:!!enumerable,
      configurable:true,
      writable:true,
    }
}

另外也会遍历数组,侦测元素的变化

拦截器

['push','pop','shift','unshift','splice','sort','reverse']
.forEach(function(method){
  //获取到原生方法
  const origin = arrProto[method]
  Object.defineProperty(arrayMethods,method,{
    value :function(...args){
    
        const ob = this._ob_ 
        ob.dep.notify()
        通知依赖,这里数据发生了变化
        
        return origin.apply(this,args)
    },
    enumerable:false,//不可枚举
    writable:true,
    configurable:true
  })
})

最后如果是push、unshift、splice这些可以新增元素的话,暂存这些元素拿去修改为存取描述符

arg 就是 push(...arg)的形参
let inserted
switch(method){
    case 'push' :
    case 'unshift' :
      inserted = arg
      break
    case 'splice':
      inserted = arg.slice(2)
}
if(inserted) ob.observeArray(inserted)

这里说一下splice为什么要arg.slice(2),splice要有至少三个参数才是添加,不然是删除

好了Vue2版本的就写到这里了,diff那块虽然读完了但懒得写了,原本19年11月就读完了源码到现在才写完。之后会去读React的与源码部分,Vue3.0等正式发布后还要去读源码。

到时候在写一些解析吧。