我的博客地址vue
像Vue官網上面說的,vue是經過Object.defineProperty
來偵測對象屬性值的變化。數組
function defineReactive (obj, key, val) { let dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { return val }, set (newVal) { if (val === newVal) return val = newVal } }) }
函數 defineReactive 是對 Object.defineProperty 的封裝,做用是定義一個響應式的數據。瀏覽器
不過若是隻是這樣是沒有什麼用的,真正有用的是收集依賴。在getter中收集依賴,在setter觸發依賴。緩存
Dep (收集依賴)閉包
// 還有幾個方法沒寫,好比怎麼移除依賴。 class Dep { constructor () { // 依賴數組 this.subs = [] } addSub (sub) { this.subs.push(sub) } depend (target) { if (Dep.target) { // 這時的Dep.target是Watcher實例 Dep.target.addDep(this) } } notity () { this.subs.forEach(val => { val.update() }) } Dep.target = null }
Watcher (依賴)app
// 原本在Watcher中也要記錄Dep,可是偷懶沒寫了,記錄了Dep後能夠通知收集了Watcher的Dep移除依賴。 class Watcher { constructor (vm, expOrFn, cb) { // vm: vue實例 // expOrFn: 字符串或函數 // cb: callback回調函數 this.vm = vm this.cb = cb // 執行this.getter就能夠讀取expOrFn的數據,就會收集依賴 if (typeof expOrFn === 'function') { this.getter = expOrFn } else { // parsePath是讀取字符串keypath的函數,具體的能夠去瀏覽Vue的源碼 this.getter = parsePath(expOrFn) } this.value = this.get() } get () { Dep.target = this // 在這裏執行this.getter let value = this.getter(this.vm, this.vm) Dep.target = null return value } addDep (dep) { dep.addSub(this) } // 更新依賴 update () { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }
接下來再改一下剛開始定義的 defineReactive 函數函數
function defineReactive (obj, key, val) { let dep = new Dep() // 閉包 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { // 觸發getter時,收集依賴 dep.addDep() return val }, set (newVal) { if (val === newVal) return val = newVal // 觸發setter時,觸發Dep的notify,便利依賴 dep.notity() } }) }
這個時候已經能夠偵測數據的單獨一個屬性,最後再封裝一下:this
class Observer { constructor (value) { this.value = value // 偵測數據的變化和偵測對象的變化是有區別的 if (!Array.isArray(value)) { this.walk(value) } } walk (value) { const keys = Object.keys(value) keys.forEach(key => { this.defineReactive(value, key, value[key]) }) } }
最後總結一下:prototype
實例化 Watcher 時經過 get 方法把 Dep.target 賦值爲當前的 Wathcer 實例,並把 Watcher 實例添加在 Dep 中,當設置數據時,觸發 defineReactive 的 set 運行 Dep.notify() 遍歷 Dep 中收集的依賴 Watcher 實例,而後觸發 Watcher 實例的 update 方法。code
Object 能夠經過 getter/setter 來偵測變化,可是數組是經過方法來變化,好比 push 。這樣就不能和對象同樣,只能經過攔截器來實現偵測變化。
定義一個攔截器來覆蓋 Array.prototype,每當使用數組原型上面的方法操做數組的時候,實際上執行的是攔截器上面的方法,而後再攔截器裏面使用 Array 的原型方法。
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { // 緩存原始方法 const original = arrayProto[method] Object.defineProperty(arrayMethods, method, { enumerable: false, configurable: true, writable: true, value: function mutator (...args) { return original.apply(this, args) } })
而後就要覆蓋 Array 的原型:
// 看是否支持__proto__, 若是不支持__proto__,則直接把攔截器的方法直接掛載到value上。 const hasProto = "__proto__" in {} const arrayKeys = Object.getOwnPropertyNames(arrayMethods) class Observer { constructor (value) { this.value = value if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) } } walk (value) { const keys = Object.keys(value) keys.forEach(key => { this.defineReactive(value, key, value[key]) }) } } function protoAugment (target, src: Object) { target.__proto__ = src } function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } }
Array 也是在 getter 中收集依賴,不過依賴存的地方有了變化。Vue.js 把依賴存在 Observer 中:
class Observer { constructor (value) { this.value = value this.dep = new Dep // 新增Dep if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) } } walk (value) { const keys = Object.keys(value) keys.forEach(key => { this.defineReactive(value, key, value[key]) }) } }
至於爲何把 Dep 存在 Observer 是由於必須在 getter 和 攔截器中都能訪問到。
function defineReactive (data, key, val) { let childOb = observer(val) // 新增 let dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { dep.addDep() if (childOb) { // 在這裏收集數組依賴 childOb.dep.depend() } return val }, set (newVal) { if (val === newVal) return val = newVal dep.notity() } }) } // 若是value已是響應式數據,即有了__ob__屬性,則直接返回已經建立的Observer實例 // 若是不是響應式數據,則建立一個Observer實例 function observer (value, asRootData) { if (!isObject(value)) { return } let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observe) { ob = value.__ob__ } else { ob = new Observer(value) } return ob }
由於攔截器是對 Array 原型的封裝,因此能夠在攔截器中訪問到this(當前正在被操做的數組),
dep保存在 Observer 實例中,因此須要在this上訪問到 Observer 實例:
function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configerable: true }) } class Observer { constructor (value) { this.value = value this.dep = new Dep // 把value上新增一個不可枚舉的屬性__ob__,值爲當前的Observer實例 // 這樣就能夠經過數組的__ob__屬性拿到Observer實例,而後就能夠拿到Observer的depp // __ob__不止是爲了拿到Observer實例,還能夠標記是不是響應式數據 def(value, '__ob__', this) // 新增 if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) } } ... }
在攔截器中:
methodsToPatch.forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ // 新增 ob.dep.notify() // 新增 向依賴發送信息 return resullt }) })
到這裏還只是偵測了數組的變化,還要偵測數組元素的變化:
class Observer { constructor (value) { this.value = value this.dep = new Dep def(value, '__ob__', this) if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) // 偵測數組中的每一項 this.observeArray(value) // 新增 } } observeArray (items) { items.forEach(item => { observe(item) }) } ... }
而後還要偵測數組中的新增元素的變化:
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 breaak case 'splice' inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 新增結束 ob.dep.notify() return resullt }) })
總結一下:
Array 追蹤變化的方式和 Object 不同,是經過攔截器去覆蓋數組原型的方法來追蹤變化。
爲了避免污染全局的 Array.prototype ,因此只針對那些須要偵測變化的數組,對於不支持 __proto__
的瀏覽器則直接把攔截器佈置到數組自己上。
在 Observer 中,對每一個偵測了變化的數據都加了 __ob__
屬性,而且把this(Observer實例)
保存在__ob__
上,主要有兩個做用:
__ob__
,進一步拿到 Observer 實例。因此把數組的依賴存放在 Observer 中,當攔截到數組發生變化時,向依賴發送通知。
最後還要經過observeArray
偵測數組子元素和數組新增元素的變化。