當前前端界空前繁榮,各類框架橫空出世,包括各種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 ~