源碼學習VUE之Observe

在文章 源碼學習VUE之響應式原理咱們大概描述了響應式的實現流程,主要寫了observe,dep和wather的簡易實現,以及推導思路。但相應代碼邏輯並不完善,今天咱們再來填以前的一些坑。數組

Observe

以前實現的observe函數只能處理一個對象的單個屬性,但咱們更多的數據是保存在對象中,爲了抽象話,咱們也封裝一個對象Observe,只要傳進一個參數,就能夠把這個對象進行監聽。app

對現有全部屬性進行監聽

var obj = {
    a: 1,
    b: 2
}

好比一個對象有兩個屬性 a,b。咱們能夠嘗試寫出下面的實現類函數

class Observe{
    constructor(value){
        this.value = value //要監聽的值。
        this.walk();
    }
    
    walk(){ //經過walk函數,依次處理
        const keys = Object.keys(obj);
        let self = this;
        for (let i = 0; i < keys.length; i++) {
          self.defineReactive(obj, keys[i])
        }
    }
    
    defineReactive (data, key, val) {
        var dep = new Dep();
        Object.defineProperty(obj, a, {
            enumerable: true,
            configurable: true,
            get: function(){
               if(Dep.target){
                    dep.addSub(Dep.target); // Dep.target是Watcher的實例
                }
            },
            set: function(newVal){
                if(val === newVal) return
                val = newVal;
                dep.notify();
            }
        })
    }
}

固然,爲了防止重複監聽,咱們能夠給原object設置一個標識符以做辨別。學習

class Obsever(){
    construct(){
        this.value = value //要監聽的值。
        Object.defineProperty(value, "__ob__", {
            value: this,
            enumerable: false,
            writable: true,
            configurable: true
        })
        this.walk();
    }
}

監聽數組

雖然數組也是一個對象,可是咱們隊數組的操做卻不會觸發set,get方法。所以必須對數組特殊處理。
首先須要對操做數組的方法進行改寫,如push,pop,shift優化

//首先拿到Array的原生原型鏈
const arrayProto = Arrary.prototype;
//爲了保證修改不會影響原生方法,咱們建立一個新對象
const arrayMethods = Object.create(arrayProto);
//要改寫的方法
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method] // 先拿到原生方法
  def(arrayMethods, method, function mutator (...args) {
  // 改寫後的方法,都是先拿到原生方法的計算結果
    const result = original.apply(this, args)
    const ob = this.__ob__
    // 拿到插入的值。
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    //Observe插入的值
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

其實邏輯很簡單。對於能夠改變array的方法咱們都改寫一下。只要調用了這些方法,除了返回正確的值,咱們都通知觀察對象,數據改變了,觸發觀察者update操做。同時,數組裏面多是個對象,咱們不改變數組自己,可是改變數組裏面的某個值,這也算是一種改變,所以,除了監聽數組自己的改變,也要對數組每一個值進行observe。
這涉及到兩點,一是observe Array的時候,就要對每一個值進行Observe。另外,插入數組的每一個值也要observe.第二點就是上面代碼中特別關注push,unshift,splice這三個能夠插值方法的緣由。this

class Obsever(){
    construct(){
        this.value = value //要監聽的值。
        Object.defineProperty(value, "__ob__", {
            value: this,
            enumerable: false,
            writable: true,
            configurable: true
        })
        if(Array.isArray(value)){
            this.observeArray();
        }else{
             this.walk();
        }
    }
    observeArray(items){
       for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        } 
    }
},

function observe (value) {
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  // 若是已經observe的對象就再也不進行重複的observe操做
    ob = value.__ob__
  } else {
        ob = new Observer(value)
  }
  return ob
}

優化

實際開發中咱們常常會遇到一個很大的數據。如渲染tables時,table的數據極可能很大(一個多多維數組)。若是都進行observe無心會是很大的開銷。關鍵是咱們只是須要拿這些數據來渲染,並不關心數據內部的變化。所以可能就存在這種需求,能夠不對array或object深層遍歷observe。咱們可使用Object.freeze()將這個數據凍結起來。
所以對於凍結的數據咱們就再也不進行observe。上面的代碼能夠這麼優化prototype

function observe (value) {
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  // 若是已經observe的對象就再也不進行重複的observe操做
    ob = value.__ob__
  } else if(Object.isExtensible(value)){// 若是數據被凍結,或者不可擴展,則不進行observe操做
        ob = new Observer(value)
  }
  return ob
}

 defineReactive (data, key, val) {
        var dep = new Dep();
        var property = Object.getOwnPropertyDescriptor(obj, key)
        // 若是數據被凍結,或者不可擴展,則改寫set,get方法
          if (property && property.configurable === false) {
            return
          }
          //傳進來的對象可能以前已經被定義了set,get方法,所以咱們不能直接拿value
        var getter = property && property.get
        var setter = property && property.set
        Object.defineProperty(obj, a, {
            enumerable: true,
            configurable: true,
            get: function(){
               var value = getter ? getter.call(obj) : val;
               if(Dep.target){
                    dep.addSub(Dep.target); // Dep.target是Watcher的實例
                }
                return value
            },
            set: function(newVal){
                if(val === newVal) return
                val = newVal;
                dep.notify();
            }
        })
    }
相關文章
相關標籤/搜索