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ù)的響應式(目前只有劫持)
3.observe 方法最終返回一個 Observer 類
4.Observer 類初始化時,通過 walk 遍歷屬性
5.對每一個屬性進行 defineReactive(Object.defineProperty)就實現(xiàn)對象屬性的單層數(shù)據(jù)劫持
6.在 defineReactive 中,如果屬性值為對象類型就繼續(xù)調(diào)用 observe 對當前的對象屬性進行觀測(即遞歸步驟 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: {} } } } });
當 data 中的屬性為數(shù)組時,Vue 是如何進行處理的
三,數(shù)組類型的處理
1,當前邏輯分析
按照當前版本的處理邏輯,所有對象類型會對被進行深層觀測,數(shù)組也不例外
let vm = new Vue({ el: '#app', data() { return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]} } });
可以看到,數(shù)組中的每一項,都被添加了 get、set 方法,也就相當于實現(xiàn)了對數(shù)組的深層觀測
備注:Object.defineProperty支持數(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ù)組中每一項進行處理
還有就是,雖然數(shù)組能夠通過 defineProperty 實現(xiàn)對索引更新劫持
但在實際開發(fā)場景真的需要嗎?似乎很少會使用 arr[888] = x
這種操作
所以,權(quán)衡性能和需求,Vue 源碼中沒有采用 defineProperty 對數(shù)組進行處理
當然,這也就導致了在 Vue 中無法通過直接修改索引、length 觸發(fā)視圖的更新
3,數(shù)組的劫持思路
核心目標是要實現(xiàn)數(shù)組的響應式:
Vue 認為這 7 個方法能夠改變原數(shù)組:push、pop、splice、shift、unshift、reverse、sort
所以,只要對這 7 個方法進行處理,就能劫持到數(shù)組的數(shù)據(jù)變化,實現(xiàn)數(shù)組數(shù)據(jù)的響應式
備注:這種實現(xiàn)思路,也直接導致了 vue2 修改數(shù)組的索引和長度不能觸發(fā)視圖更新
梳理對象屬性深層劫持的實現(xiàn):
- 數(shù)據(jù)觀測入口:src/observe/index.js#observe方法
- 如果數(shù)據(jù)為對象類型就 new Observer
- Observer 初始化時,會遍歷對象屬性,逐一遞歸 Object.defineProperty
數(shù)組也是對象,所以,要把數(shù)組的處理邏輯單獨拆出來。即對 7 個變異方法進行重寫
// 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ù)組類型進行單獨處理:重寫 7 個變異方法 }else{ this.walk(value); } } }
4,數(shù)組方法的攔截思路
- 重寫方法需要在原生方法基礎上,實現(xiàn)對數(shù)據(jù)變化的劫持操作
- 僅對響應式數(shù)據(jù)中的數(shù)組進行方法重寫,不能影響非響應式數(shù)組
所以,對響應式數(shù)據(jù)中數(shù)組這 7 個方法進行攔截,即優(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ù)組自身上進行方法重寫,對鏈上的同名方法進行攔截 methods.forEach(method => { arrayMethods[method] = function () { console.log('數(shù)組的方法進行重寫操作 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 進行遞歸劫持,而是通過對能夠?qū)е略瓟?shù)組變化的 7 個方法進行攔截和重寫實現(xiàn)了數(shù)據(jù)劫持
到此這篇關(guān)于Vue數(shù)組的劫持逐步分析講解的文章就介紹到這了,更多相關(guān)Vue數(shù)組劫持內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue中使用highlight.js實現(xiàn)代碼高亮顯示以及點擊復制
本文主要介紹了Vue中使用highlight.js實現(xiàn)代碼高亮顯示以及點擊復制,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01vue.js動畫中的js鉤子函數(shù)的實現(xiàn)
這篇文章主要介紹了vue.js動畫中的js鉤子函數(shù)的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07Vue 基礎語法之計算屬性(computed)、偵聽器(watch)、過濾器(filters)詳解
計算屬性就是 Vue 實例選項中的 computed,computed 的值是一個對象類型,對象中的屬性值為函數(shù),而且這個函數(shù)沒辦法接收參數(shù),這篇文章主要介紹了Vue 基礎語法之計算屬性(computed)、偵聽器(watch)、過濾器(filters)詳解,需要的朋友可以參考下2022-11-11vue路由跳轉(zhuǎn)打開新窗口(window.open())和關(guān)閉窗口(window.close())
這篇文章主要介紹了vue路由跳轉(zhuǎn)打開新窗口(window.open())和關(guān)閉窗口(window.close())問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04vue element-ui表格自定義動態(tài)列具體實現(xiàn)
這周工作中遇見了一個表格動態(tài)列的需求,下面這篇文章主要給大家介紹了關(guān)于vue element-ui表格自定義動態(tài)列具體實現(xiàn)的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-06-06