Vue數(shù)組的劫持逐步分析講解
一,前言
上篇,主要介紹了 Vue 數(shù)據(jù)初始化流程中,對(duì)象屬性的深層劫持是如何實(shí)現(xiàn)的
核心思路就是遞歸,主要流程如下;
1.通過(guò) data = isFunction(data) ? data.call(vm) : data;處理后的 data 一定是對(duì)象類(lèi)型
2.通過(guò) data = observe(data)處理后的 data 就實(shí)現(xiàn)了數(shù)據(jù)的響應(yīng)式(目前只有劫持)
3.observe 方法最終返回一個(gè) Observer 類(lèi)
4.Observer 類(lèi)初始化時(shí),通過(guò) walk 遍歷屬性
5.對(duì)每一個(gè)屬性進(jìn)行 defineReactive(Object.defineProperty)就實(shí)現(xiàn)對(duì)象屬性的單層數(shù)據(jù)劫持
6.在 defineReactive 中,如果屬性值為對(duì)象類(lèi)型就繼續(xù)調(diào)用 observe 對(duì)當(dāng)前的對(duì)象屬性進(jìn)行觀測(cè)(即遞歸步驟 3~5),這樣就實(shí)現(xiàn)了對(duì)象屬性的深層數(shù)據(jù)劫持
本篇,繼續(xù)介紹 Vue 數(shù)據(jù)初始化流程中,對(duì)于數(shù)組類(lèi)型的劫持
二,對(duì)象劫持回顧
1,Demo
data 數(shù)據(jù)中對(duì)象屬性的深層觀測(cè),即對(duì)象屬性為對(duì)象(包含多層)的情況
let vm = new Vue({ el: '#app', data() { return { message: 'Hello Vue', obj: { key: "val" }, a: { a: { a: {} } } } });
當(dāng) data 中的屬性為數(shù)組時(shí),Vue 是如何進(jìn)行處理的
三,數(shù)組類(lèi)型的處理
1,當(dāng)前邏輯分析
按照當(dāng)前版本的處理邏輯,所有對(duì)象類(lèi)型會(huì)對(duì)被進(jìn)行深層觀測(cè),數(shù)組也不例外
let vm = new Vue({ el: '#app', data() { return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]} } });
可以看到,數(shù)組中的每一項(xiàng),都被添加了 get、set 方法,也就相當(dāng)于實(shí)現(xiàn)了對(duì)數(shù)組的深層觀測(cè)
備注:Object.defineProperty支持?jǐn)?shù)組數(shù)據(jù)類(lèi)型的劫持
2,Vue 對(duì)性能的權(quán)衡
在 Vue2.x 中,不支持通過(guò)修改數(shù)組索引和長(zhǎng)度的數(shù)據(jù)劫持;
那么,為什么原本可以實(shí)現(xiàn)對(duì)數(shù)組索引的觀測(cè),Vue 卻選擇了不支持呢?
主要是考慮了性能問(wèn)題,比如,數(shù)組中的數(shù)據(jù)量非常大時(shí):
let vm = new Vue({ el: '#app', data() { return { arr:new Array(9999) } } });
這時(shí),數(shù)組中 9999 條數(shù)據(jù),將全部被添加 get、set 方法
而這一套操作就比較費(fèi)勁了:為了實(shí)現(xiàn)數(shù)組索引劫持,需要對(duì)數(shù)組中每一項(xiàng)進(jìn)行處理
還有就是,雖然數(shù)組能夠通過(guò) defineProperty 實(shí)現(xiàn)對(duì)索引更新劫持
但在實(shí)際開(kāi)發(fā)場(chǎng)景真的需要嗎?似乎很少會(huì)使用 arr[888] = x
這種操作
所以,權(quán)衡性能和需求,Vue 源碼中沒(méi)有采用 defineProperty 對(duì)數(shù)組進(jìn)行處理
當(dāng)然,這也就導(dǎo)致了在 Vue 中無(wú)法通過(guò)直接修改索引、length 觸發(fā)視圖的更新
3,數(shù)組的劫持思路
核心目標(biāo)是要實(shí)現(xiàn)數(shù)組的響應(yīng)式:
Vue 認(rèn)為這 7 個(gè)方法能夠改變?cè)瓟?shù)組:push、pop、splice、shift、unshift、reverse、sort
所以,只要對(duì)這 7 個(gè)方法進(jìn)行處理,就能劫持到數(shù)組的數(shù)據(jù)變化,實(shí)現(xiàn)數(shù)組數(shù)據(jù)的響應(yīng)式
備注:這種實(shí)現(xiàn)思路,也直接導(dǎo)致了 vue2 修改數(shù)組的索引和長(zhǎng)度不能觸發(fā)視圖更新
梳理對(duì)象屬性深層劫持的實(shí)現(xiàn):
- 數(shù)據(jù)觀測(cè)入口:src/observe/index.js#observe方法
- 如果數(shù)據(jù)為對(duì)象類(lèi)型就 new Observer
- Observer 初始化時(shí),會(huì)遍歷對(duì)象屬性,逐一遞歸 Object.defineProperty
數(shù)組也是對(duì)象,所以,要把數(shù)組的處理邏輯單獨(dú)拆出來(lái)。即對(duì) 7 個(gè)變異方法進(jìn)行重寫(xiě)
// 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)){ // 對(duì)數(shù)組類(lèi)型進(jìn)行單獨(dú)處理:重寫(xiě) 7 個(gè)變異方法 }else{ this.walk(value); } } }
4,數(shù)組方法的攔截思路
- 重寫(xiě)方法需要在原生方法基礎(chǔ)上,實(shí)現(xiàn)對(duì)數(shù)據(jù)變化的劫持操作
- 僅對(duì)響應(yīng)式數(shù)據(jù)中的數(shù)組進(jìn)行方法重寫(xiě),不能影響非響應(yīng)式數(shù)組
所以,對(duì)響應(yīng)式數(shù)據(jù)中數(shù)組這 7 個(gè)方法進(jìn)行攔截,即優(yōu)先使用重寫(xiě)方法,其他方法還走原生邏輯
數(shù)組方法的查找,先查找自己身上的方法(即重寫(xiě)方法),找不到再去鏈上查(原生方法)
5,數(shù)組方法重寫(xiě)的實(shí)現(xiàn)
// src/Observer/array.js // 拿到數(shù)組的原型方法 let oldArrayPrototype = Array.prototype; // 原型繼承,將原型鏈向后移動(dòng) arrayMethods.__proto__ == oldArrayPrototype export let arrayMethods = Object.create(oldArrayPrototype); // 重寫(xiě)能夠?qū)е略瓟?shù)組變化的七個(gè)方法 let methods = [ 'push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice' ] // 在數(shù)組自身上進(jìn)行方法重寫(xiě),對(duì)鏈上的同名方法進(jìn)行攔截 methods.forEach(method => { arrayMethods[method] = function () { console.log('數(shù)組的方法進(jìn)行重寫(xiě)操作 method = ' + method) } });
添加 new Observer 時(shí),對(duì)數(shù)組方法重寫(xiě)的邏輯:
// src/observe/index.js import { arrayMethods } from "./array"; class Observer { constructor(value) { // 分別處理 value 為數(shù)組和對(duì)象兩種情況 if(isArray(value)){ value.__proto__ = arrayMethods; // 更改數(shù)組的原型方法 }else{ this.walk(value); } } }
測(cè)試數(shù)組方法的重寫(xiě):
數(shù)組的鏈:
- array.proto:包含 7 個(gè)重寫(xiě)方法
- array.proto.proto:原始方法
6,數(shù)組方法攔截的實(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ù)組的原型方法已完成重寫(xiě) // 測(cè)試數(shù)組方法的攔截效果 data.arr.push(666); data.arr.pop() }
- arrayMethods.push:會(huì)在數(shù)組自身找到重寫(xiě)的push方法,不會(huì)繼續(xù)到鏈上查找,實(shí)現(xiàn)攔截
- arrayMethods.pop:數(shù)組自身沒(méi)找到重寫(xiě)方法,繼續(xù)到鏈上找到原生pop方法
四,結(jié)尾
本篇主要介紹了 Vue 數(shù)據(jù)初始化流程中,數(shù)組類(lèi)型的數(shù)據(jù)劫持,核心有以下幾點(diǎn):
出于對(duì)性能的考慮,Vue 沒(méi)有對(duì)數(shù)組類(lèi)型的數(shù)據(jù)使用 Object.defineProperty 進(jìn)行遞歸劫持,而是通過(guò)對(duì)能夠?qū)е略瓟?shù)組變化的 7 個(gè)方法進(jìn)行攔截和重寫(xiě)實(shí)現(xiàn)了數(shù)據(jù)劫持
下一篇,數(shù)據(jù)代理的實(shí)現(xiàn)
到此這篇關(guān)于Vue數(shù)組的劫持逐步分析講解的文章就介紹到這了,更多相關(guān)Vue數(shù)組劫持內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中簡(jiǎn)單彈框dialog的實(shí)現(xiàn)方法
下面小編就為大家分享一篇vue中簡(jiǎn)單彈框dialog的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Vue2實(shí)現(xiàn)自適應(yīng)屏幕大小的兩種方法詳解
這篇文章主要為大家詳細(xì)介紹了Vue2實(shí)現(xiàn)自適應(yīng)屏幕大小的兩種方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03Vue中使用highlight.js實(shí)現(xiàn)代碼高亮顯示以及點(diǎn)擊復(fù)制
本文主要介紹了Vue中使用highlight.js實(shí)現(xiàn)代碼高亮顯示以及點(diǎn)擊復(fù)制,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01vue.js動(dòng)畫(huà)中的js鉤子函數(shù)的實(shí)現(xiàn)
這篇文章主要介紹了vue.js動(dòng)畫(huà)中的js鉤子函數(shù)的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07VUE組件中的 Drawer 抽屜實(shí)現(xiàn)代碼
這篇文章主要介紹了VUE組件 之 Drawer 抽屜 ,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08Vue 基礎(chǔ)語(yǔ)法之計(jì)算屬性(computed)、偵聽(tīng)器(watch)、過(guò)濾器(filters)詳解
計(jì)算屬性就是 Vue 實(shí)例選項(xiàng)中的 computed,computed 的值是一個(gè)對(duì)象類(lèi)型,對(duì)象中的屬性值為函數(shù),而且這個(gè)函數(shù)沒(méi)辦法接收參數(shù),這篇文章主要介紹了Vue 基礎(chǔ)語(yǔ)法之計(jì)算屬性(computed)、偵聽(tīng)器(watch)、過(guò)濾器(filters)詳解,需要的朋友可以參考下2022-11-11vue路由跳轉(zhuǎn)打開(kāi)新窗口(window.open())和關(guān)閉窗口(window.close())
這篇文章主要介紹了vue路由跳轉(zhuǎn)打開(kāi)新窗口(window.open())和關(guān)閉窗口(window.close())問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04vue element-ui表格自定義動(dòng)態(tài)列具體實(shí)現(xiàn)
這周工作中遇見(jiàn)了一個(gè)表格動(dòng)態(tài)列的需求,下面這篇文章主要給大家介紹了關(guān)于vue element-ui表格自定義動(dòng)態(tài)列具體實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06