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