Vue核心之數據劫持

 

 

前瞻

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

什麼是數據劫持

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

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這個方法,具體用在哪裏而且它又解決了哪些問題,下面作詳細的介紹:web

監聽對象屬性的變化

這個應該是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()//這個是真正劫持的目的,要對訂閱者發通知了
    }
  })
}

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

監聽數組的變化

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對象:jsp

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

按理說咱們操做數據的時候應該是VM.data.name = ‘hxx’纔對,可是做者以爲這樣不夠簡潔,因此又經過代理的方式實現了VM.name = ‘hxx’的可能。 相關代碼以下:mvvm

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方法劫持實現的。ide

總結

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

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

相關文章
相關標籤/搜索