【每日一題】【vue2源碼學習】vue如何檢測數組的變化


數組可以用defineProperty進行監聽。但是考慮性能原因,不能數組一百萬項每一項都循環監聽(那樣性能太差了)。所以沒有使用Ojbect.defineProperty對數組每一項進行攔截,而是選擇劫持數組原型上的個別方法並重寫。

具體重寫的有:

pushpopshiftunshiftsortreversesplice (這七個都是會改變原數組的)

另外要注意的是:

不是直接粗暴重寫了Array.prototype上的push等方法,而是通過原型鏈繼承與函數劫持進行的移花接木。並且只監聽調用了defineReactive函數時傳進來的數組。

具體實現思路:

以push為例,而是利用Object.create(Array.prototype)生成新的數組對象,該對象的__proto__指向Array.prototype。並在對象身上創建push等函數,利用函數劫持,在函數內部Array.prototype.push.call調用原有push方法,並執行自己劫持的代碼(如視圖更新)。最后將需要綁定的數組的__proto__由指向Array.prototype改向指成擁有重寫方法的新數組對象。具體看下邊源碼仿寫,真實Array.prototype里的祖宗級別push等方法沒有動。

思考:

為啥不重寫map等也是修改原數組的方法呢?

特別注意:

在Vue中修改數組的索引和長度,是無法被監控到並做響應式視圖更新的。需要通過以上7種變異方法修改數組才會觸發數組對應watcher進行更新。 數組中如果是對象數據類型的也會進行遞歸劫持。 如果情節需要,通過索引來修改數組里的內容。可以通過Vue.$set()方法來進行處理,或者使用splice方法實現。(其實$set內部的核心也是splice方法)

原理mock:

vue【數組】響應式數據原理mock

let state = [1,2,3]; //待監聽的數據

// 1、響應式數據-函數劫持實現數組原型方法重寫
let OriginalArray = Array.prototype; 
/* 
  並不是直接改寫原型上的方法。而是給當前待監聽的數組
  原型鏈上加了push等方法劫持了Array原型的push方法。 
*/

let arrayMethods = Object.create(OriginalArray) 
/* 
  創建一個新對象(對象or數組由第一個參數決定)
  帶着指定的原型對象(Array.prototype) 
*/

console.log(
  arrayMethods,
   // 原型修改
  arrayMethods.__proto__ === OriginalArray,
  arrayMethods.__proto__ === Array.prototype
)
function defineReactive(obj{
  // 【函數劫持】改寫這個新對象身上的push、splice等數組方法
  arrayMethods.push = function(...args){
    // 並還是調用原生的push方法
    OriginalArray.push.apply(this, args) // 或者用call(this, ...args)
    // 然后這里邊做自己的事情,比如視圖更新(具體源碼怎么更新的視圖?)
    render()
  }
  // 
  obj.__proto__ = arrayMethods
/* 
  修改傳進來的、被監聽的數組的原型鏈,鏈接數組與被重寫的方法。
  原本__proto__指向Array.prototype,現在中間給他包了一層,指向我們重寫的原型方法。
  並在重寫的原型方法里再調用Array.prototype的同名原型方法。 
*/

}
defineReactive(state);
// 操作dom
function render({
  app.innerHTML = state;
}
render()
// 更改數據,觀察dom修改
btn.onclick = () => {
  state.push(state[state.length - 1] + 1)
}

源碼位置:

github:src/core/observer/array.js:8


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM