Vue2.0變化偵測Array類型的處理

爲何不能使用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有元編程能力)bash

因此爲了攔截原型方法,實現攔截器覆蓋Array.prototype。app

攔截器

攔截器原理

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的依賴收集ui

Array在get中收集依賴,在攔截器中觸發依賴this

依賴保存在哪裏

這個依賴要保存在get和攔截器都能訪問的地方spa

那就是Observer(將變量從數據描述符變爲存取描述符的函數)中,而且這個Observer保存於值Value的不可枚舉_ob_中,你能夠打開Vue看一下。prototype

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等正式發佈後還要去讀源碼。

到時候在寫一些解析吧。

相關文章
相關標籤/搜索