Vue數(shù)組的劫持逐步分析講解
一,前言
上篇,主要介紹了 Vue 數(shù)據(jù)初始化流程中,對象屬性的深層劫持是如何實現(xiàn)的
核心思路就是遞歸,主要流程如下;
1.通過 data = isFunction(data) ? data.call(vm) : data;處理后的 data 一定是對象類型
2.通過 data = observe(data)處理后的 data 就實現(xiàn)了數(shù)據(jù)的響應(yīng)式(目前只有劫持)
3.observe 方法最終返回一個 Observer 類
4.Observer 類初始化時,通過 walk 遍歷屬性
5.對每一個屬性進(jìn)行 defineReactive(Object.defineProperty)就實現(xiàn)對象屬性的單層數(shù)據(jù)劫持
6.在 defineReactive 中,如果屬性值為對象類型就繼續(xù)調(diào)用 observe 對當(dāng)前的對象屬性進(jìn)行觀測(即遞歸步驟 3~5),這樣就實現(xiàn)了對象屬性的深層數(shù)據(jù)劫持
本篇,繼續(xù)介紹 Vue 數(shù)據(jù)初始化流程中,對于數(shù)組類型的劫持
二,對象劫持回顧
1,Demo
data 數(shù)據(jù)中對象屬性的深層觀測,即對象屬性為對象(包含多層)的情況
let vm = new Vue({
el: '#app',
data() {
return { message: 'Hello Vue', obj: { key: "val" }, a: { a: { a: {} } } }
});

當(dāng) data 中的屬性為數(shù)組時,Vue 是如何進(jìn)行處理的
三,數(shù)組類型的處理
1,當(dāng)前邏輯分析
按照當(dāng)前版本的處理邏輯,所有對象類型會對被進(jìn)行深層觀測,數(shù)組也不例外
let vm = new Vue({
el: '#app',
data() {
return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]}
}
});

可以看到,數(shù)組中的每一項,都被添加了 get、set 方法,也就相當(dāng)于實現(xiàn)了對數(shù)組的深層觀測
備注:Object.defineProperty支持?jǐn)?shù)組數(shù)據(jù)類型的劫持
2,Vue 對性能的權(quán)衡
在 Vue2.x 中,不支持通過修改數(shù)組索引和長度的數(shù)據(jù)劫持;
那么,為什么原本可以實現(xiàn)對數(shù)組索引的觀測,Vue 卻選擇了不支持呢?
主要是考慮了性能問題,比如,數(shù)組中的數(shù)據(jù)量非常大時:
let vm = new Vue({
el: '#app',
data() {
return { arr:new Array(9999) }
}
});
這時,數(shù)組中 9999 條數(shù)據(jù),將全部被添加 get、set 方法
而這一套操作就比較費勁了:為了實現(xiàn)數(shù)組索引劫持,需要對數(shù)組中每一項進(jìn)行處理
還有就是,雖然數(shù)組能夠通過 defineProperty 實現(xiàn)對索引更新劫持
但在實際開發(fā)場景真的需要嗎?似乎很少會使用 arr[888] = x 這種操作
所以,權(quán)衡性能和需求,Vue 源碼中沒有采用 defineProperty 對數(shù)組進(jìn)行處理
當(dāng)然,這也就導(dǎo)致了在 Vue 中無法通過直接修改索引、length 觸發(fā)視圖的更新
3,數(shù)組的劫持思路
核心目標(biāo)是要實現(xiàn)數(shù)組的響應(yīng)式:
Vue 認(rèn)為這 7 個方法能夠改變原數(shù)組:push、pop、splice、shift、unshift、reverse、sort
所以,只要對這 7 個方法進(jìn)行處理,就能劫持到數(shù)組的數(shù)據(jù)變化,實現(xiàn)數(shù)組數(shù)據(jù)的響應(yīng)式
備注:這種實現(xiàn)思路,也直接導(dǎo)致了 vue2 修改數(shù)組的索引和長度不能觸發(fā)視圖更新
梳理對象屬性深層劫持的實現(xiàn):
- 數(shù)據(jù)觀測入口:src/observe/index.js#observe方法
- 如果數(shù)據(jù)為對象類型就 new Observer
- Observer 初始化時,會遍歷對象屬性,逐一遞歸 Object.defineProperty
數(shù)組也是對象,所以,要把數(shù)組的處理邏輯單獨拆出來。即對 7 個變異方法進(jìn)行重寫
// src/utils
/**
* 判斷是否是數(shù)組
* @param {*} val
* @returns
*/
export function isArray(val) {
return Array.isArray(val)
}
// src/observe/index.js
import { arrayMethods } from "./array";
class Observer {
constructor(value) {
if(isArray(value)){
// 對數(shù)組類型進(jìn)行單獨處理:重寫 7 個變異方法
}else{
this.walk(value);
}
}
}
4,數(shù)組方法的攔截思路
- 重寫方法需要在原生方法基礎(chǔ)上,實現(xiàn)對數(shù)據(jù)變化的劫持操作
- 僅對響應(yīng)式數(shù)據(jù)中的數(shù)組進(jìn)行方法重寫,不能影響非響應(yīng)式數(shù)組
所以,對響應(yīng)式數(shù)據(jù)中數(shù)組這 7 個方法進(jìn)行攔截,即優(yōu)先使用重寫方法,其他方法還走原生邏輯
數(shù)組方法的查找,先查找自己身上的方法(即重寫方法),找不到再去鏈上查(原生方法)
5,數(shù)組方法重寫的實現(xiàn)
// src/Observer/array.js
// 拿到數(shù)組的原型方法
let oldArrayPrototype = Array.prototype;
// 原型繼承,將原型鏈向后移動 arrayMethods.__proto__ == oldArrayPrototype
export let arrayMethods = Object.create(oldArrayPrototype);
// 重寫能夠?qū)е略瓟?shù)組變化的七個方法
let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
// 在數(shù)組自身上進(jìn)行方法重寫,對鏈上的同名方法進(jìn)行攔截
methods.forEach(method => {
arrayMethods[method] = function () {
console.log('數(shù)組的方法進(jìn)行重寫操作 method = ' + method)
}
});添加 new Observer 時,對數(shù)組方法重寫的邏輯:
// src/observe/index.js
import { arrayMethods } from "./array";
class Observer {
constructor(value) {
// 分別處理 value 為數(shù)組和對象兩種情況
if(isArray(value)){
value.__proto__ = arrayMethods; // 更改數(shù)組的原型方法
}else{
this.walk(value);
}
}
}
測試數(shù)組方法的重寫:

數(shù)組的鏈:
- array.proto:包含 7 個重寫方法
- array.proto.proto:原始方法
6,數(shù)組方法攔截的實現(xiàn)
// src/state.js#initData
function initData(vm) {
let data = vm.$options.data;
data = isFunction(data) ? data.call(vm) : data;
observe(data); // 在observe方法中new Observer執(zhí)行后,數(shù)組的原型方法已完成重寫
// 測試數(shù)組方法的攔截效果
data.arr.push(666);
data.arr.pop()
}

- arrayMethods.push:會在數(shù)組自身找到重寫的push方法,不會繼續(xù)到鏈上查找,實現(xiàn)攔截
- arrayMethods.pop:數(shù)組自身沒找到重寫方法,繼續(xù)到鏈上找到原生pop方法
四,結(jié)尾
本篇主要介紹了 Vue 數(shù)據(jù)初始化流程中,數(shù)組類型的數(shù)據(jù)劫持,核心有以下幾點:
出于對性能的考慮,Vue 沒有對數(shù)組類型的數(shù)據(jù)使用 Object.defineProperty 進(jìn)行遞歸劫持,而是通過對能夠?qū)е略瓟?shù)組變化的 7 個方法進(jìn)行攔截和重寫實現(xiàn)了數(shù)據(jù)劫持
到此這篇關(guān)于Vue數(shù)組的劫持逐步分析講解的文章就介紹到這了,更多相關(guān)Vue數(shù)組劫持內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在vue中使用vant TreeSelect分類選擇組件操作
這篇文章主要介紹了在vue中使用vant TreeSelect分類選擇組件操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
Vue自定義指令實現(xiàn)對數(shù)字進(jìn)行千分位分隔
對數(shù)字進(jìn)行千分位分隔后展示應(yīng)該是大部分同學(xué)都做過的功能了吧,常規(guī)的做法通常是編寫一個工具函數(shù)來對數(shù)據(jù)進(jìn)行轉(zhuǎn)換,那么我們可不可以通過vue指令來實現(xiàn)這一功能呢,下面我們就來探索一下呢2024-02-02
解決在Vue中使用axios用form表單出現(xiàn)的問題
今天小編就為大家分享一篇解決在Vue中使用axios用form表單出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-10-10
解決vue動態(tài)下拉菜單 有數(shù)據(jù)未反應(yīng)的問題
這篇文章主要介紹了解決vue動態(tài)下拉菜單 有數(shù)據(jù)未反應(yīng)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08

