Vue核心之數據劫持


 

 

前瞻

當前前端界空前繁榮,各種框架橫空出世,包括各類mvvm框架橫行霸道,比如Anglar,Regular,Vue,React等等,它們最大的優點就是可以實現數據綁定,再也不需要手動進行DOM操作了,它們實現的原理也基本上是臟檢查或數據劫持。那么本文就以Vue框架出發,探索其中數據劫持的奧秘(本文所選取的相關代碼源自於Vue v2.0.3版本的源碼)。

什么是數據劫持

首先我們應該搞清楚什么是數據劫持,說白了就是通過Object.defineProperty()來劫持對象屬性的setter和getter操作,在數據變動時做你想要做的事情,舉個栗子:

var data = {
    name:'lhl'
}

Object.keys(data).forEach(function(key){
    Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
            console.log('get');
        },
        set:function(){
            console.log('監聽到數據發生了變化');
        }
    })
});
data.name //控制台會打印出 “get”
data.name = 'hxx' //控制台會打印出 "監聽到數據發生了變化"

上面的這個栗子可以看出,我們完全可以控制對象屬性的設置和讀取。在Vue中,作者在很多地方都非常巧妙的運用了defineProperty這個方法,具體用在哪里並且它又解決了哪些問題,下面做詳細的介紹:

監聽對象屬性的變化

這個應該是Vue非常重要的一塊,其主要思想是observer每個對象的屬性,添加到訂閱器dep中,當數據發生變化的時候發出notice通知。 相關源代碼如下:(作者采用的是ES6+flow寫的,代碼在src/core/observer/index.js模塊里面)

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  const dep = new Dep()//創建訂閱對象

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  let childOb = observe(val)//創建一個觀察者對象
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      //這里也是作者一個巧妙設計,在創建watcher實例的時候,通過調用對象的get方法往訂閱器           dep上添加這個創建的watcher實例
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)//繼續監聽新的屬性值
      dep.notify()//這個是真正劫持的目的,要對訂閱者發通知了
    }
  })
}

以上是監聽對象屬性的變化,那么下面再看看如何監聽數組的變化:

監聽數組的變化

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})

...
/**
 * Define a property.
 */
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

通過上面的代碼可以看出Vue是通過修改了數組的幾個操作的原型來實現的。

實現對象屬性代理

正常情況下我們是這樣實例化一個Vue對象:

var VM = new Vue({
    data:{
        name:'lhl'
    },
    el:'#id'
})

按理說我們操作數據的時候應該是VM.data.name = ‘hxx’才對,但是作者覺得這樣不夠簡潔,所以又通過代理的方式實現了VM.name = ‘hxx’的可能。 相關代碼如下:

function proxy (vm, key) {
  if (!isReserved(key)) {
    Object.defineProperty(vm, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter () {
        return vm._data[key]
      },
      set: function proxySetter (val) {
        vm._data[key] = val;
      }
    });
  }
}

表面上看起來我們是在操作VM.name,實際上還是通過Object.defineProperty()中的get和set方法劫持實現的。

總結

Vue框架很好的利用了Object.defineProperty()這個方法來實現了數據的監聽和修改,同時也達到了很好的模塊間解耦,在日常開發用好這個方法說不定會達到令人意想不到的結果。

本篇文章是我對Vue的淺薄之悟,如有理解不足之處,還請大家批評指正,Thank you ~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM