看這篇以前,若是沒有看過以前的文章,可拉到文章末尾查看以前的文章。vue
在前面的幾個 step
中,咱們實現對象的屬性的監聽,可是有關於數組的行爲咱們一直沒有處理。
咱們先分析下致使數組有哪些行爲:git
arr.splice(1, 2, 'something1', 'someting2')
arr[1] = 'something'
首先咱們知道數組下的一些方法是會對原數組照成影響的,有如下幾個:github
這幾個方法總的來講會照成幾個影響:數組
不像對象,若是對象的 key
值的順序發生變化,是不會影響視圖的變化,但數組的順序若是發生變化,視圖是要變化的。app
也就是說當着幾個方法觸發的時候,咱們須要視圖的更新,也就是要觸發 Dep
中的 notify
函數。函數
可是縱觀咱們如今實現的代碼( step5
中的代碼),咱們並無特意的爲數組提供一個 Dep
。測試
而且上述的幾個數組方法是數組對象提供的,咱們要想辦法去觸發 Dep
下的 notify
函數。優化
咱們先爲數組提供一個 Dep
,完善後的 Observer
:this
export class Observer { constructor(value) { this.value = value if (Array.isArray(value)) { // 爲數組設置一個特殊的 Dep this.dep = new Dep() this.observeArray(value) } else { this.walk(value) } Object.defineProperty(value, '__ob__', { value: this, enumerable: false, writable: true, configurable: true }) } /** * 遍歷對象下屬性,使得屬性變成可監聽的結構 */ walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * 同上,遍歷數組 */ observeArray (items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
一樣的在 defineReactive
咱們須要處理數組添加依賴的邏輯prototype
export function defineReactive(object, key, value) { let dep = new Dep() let childOb = observe(value) Object.defineProperty(object, key, { configurable: true, enumerable: true, get: function () { if (Dep.target) { dep.addSub(Dep.target) Dep.target.addDep(dep) // 處理數組的依賴 if(Array.isArray(value)){ childOb.dep.addSub(Dep.target) Dep.target.addDep(childOb.dep) } } return value }, set: function (newValue) { if (newValue !== value) { value = newValue dep.notify() } } }) }
ok 咱們如今完成了依賴的添加,剩下的咱們要實現依賴的觸發。
處理方法:在數組對象調用特定方法時,首先找到的應該是咱們本身寫的方法,而這個方法中調用了原始方法,並觸發依賴。
咱們先來包裝一下方法,獲得一些同名方法:
const arrayProto = Array.prototype // 複製方法 export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * 改變數組的默認處理,將新添加的對象添加監聽 */ methodsToPatch.forEach(function (method) { // 原始的數組處理方法 const original = arrayProto[method] let mutator = function (...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 } // 新添加的對象須要添加監聽 if (inserted) ob.observeArray(inserted) // 觸發 notify 方法 ob.dep.notify() return result } Object.defineProperty(arrayMethods, method, { value: mutator, enumerable: false, writable: true, configurable: true }) })
ok 咱們如今獲得了一些列同名的方法,我只要確保在調用時,先調用到咱們的方法便可。
有兩種方式能夠實現:
__proto__
,這樣尋找原型鏈時,就會先找到咱們的方法具體到代碼中的實現:
export class Observer { constructor(value) { this.value = value if (Array.isArray(value)) { this.dep = new Dep() const augment = ('__proto__' in {}) ? protoAugment : copyAugment // 覆蓋數組中一些改變了原數組的方法,使得方法得以監聽 augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } ... } ... } /** * 若是能使用 __proto__ 則將數組的處理方法進行替換 */ function protoAugment (target, src, keys) { target.__proto__ = src } /** * 若是不能使用 __proto__ 則直接將該方法定義在當前對象下 */ function copyAugment (target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] Object.defineProperty(target, key, { value: src[key], enumerable: false, writable: true, configurable: true }) } }
測試一下:
let object = { arrayTest: [1, 2, 3, 4, 5] } observe(object) let watcher = new Watcher(object, function () { return this.arrayTest.reduce((sum, num) => sum + num) }, function (newValue, oldValue) { console.log(`監聽函數,數組內全部元素 = ${newValue}`) }) object.arrayTest.push(10) // 監聽函數,數組內全部元素 = 25
到如今爲止,咱們成功的在數組調用方法的時候,添加並觸發了依賴。
首先先說明,數組下的索引是和對象下的鍵有一樣的表現,也就是能夠用 defineReactive
來處理索引值,可是數組是用來存放一系列的值,咱們並不能一開始就肯定數組的長度,而且極有可能剛開始數組長度爲 0
,以後數組中的索引對應的內容也會不斷的變化,因此爲索引調用 defineReactive
是不切實際的。
可是相似於 arr[1] = 'something'
這樣的賦值在數組中也是常見的操做,在 Vue
中實現了 $set
具體的細節這裏不談,這裏實現了另外一種方法,咱們僅僅須要在數組對象下添加一個方法便可:
arrayMethods.$apply = function () { this.__ob__.observeArray(this) this.__ob__.dep.notify() }
測試一下:
object.arrayTest[1] = 10 object.arrayTest.$apply() // 監聽函數,數組內全部元素 = 33
到目前爲了,一個完整的數據監聽的模型也就完成了,咱們可使用 observe
方法來獲得一個可監聽結構,而後用 Watcher
添加依賴。
在設置值的時候就能成功觸發依賴。