Vue2?響應(yīng)式系統(tǒng)之?dāng)?shù)組
本文接Vue2響應(yīng)式系統(tǒng) 、Vue2 響應(yīng)式系統(tǒng)之分支切換 ,響應(yīng)式系統(tǒng)之嵌套、響應(yīng)式系統(tǒng)之深度響應(yīng) 還沒有看過的小伙伴需要看一下。
1、場(chǎng)景
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list = ["hello", "liang"];
先可以一分鐘思考下會(huì)輸出什么。
雖然 的值是數(shù)組,但我們是對(duì) 進(jìn)行整體賦值,所以依舊會(huì)觸發(fā) 的 ,觸發(fā) 進(jìn)行重新執(zhí)行,輸出如下:list
data.list
data.list
set
Watcher
2、場(chǎng)景 2
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list.push("liang");
先可以一分鐘思考下會(huì)輸出什么。
這次是調(diào)用 方法,但我們對(duì) 方法什么都沒做,因此就不會(huì)觸發(fā) 了。push
push
Watcher
3、方案
為了讓 還有數(shù)組的其他方法也生效,我們需要去重寫它們,通過push
代理模式 我們可以將數(shù)組的原方法先保存起來,然后執(zhí)行,并且加上自己額外的操作。
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ /* export function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true, }); } */ import { def } from "./util"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); /*****************這里相當(dāng)于調(diào)用了對(duì)象 set 需要通知 watcher ************************/ // 待補(bǔ)充 /**************************************************************************** */ return result; }); });
當(dāng)調(diào)用了數(shù)組的 或者其他方法,就相當(dāng)于我們之前重寫屬性的 ,上邊待補(bǔ)充的地方需要做的就是通知 中的 。push
set
dep
Watcher
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 讀取用戶可能自己定義了的 get、set const getter = property && property.get; const setter = property && property.set; // val 沒有傳進(jìn)來話進(jìn)行手動(dòng)賦值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一個(gè) Dep 對(duì)象,用來保存所有依賴于該變量的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } dep.notify(); }, }); }
如上邊的代碼,之前的 是通過閉包,每一個(gè)屬性都有一個(gè)各自的 ,負(fù)責(zé)收集 和通知 。dep
dep
Watcher
Watcher
那么對(duì)于數(shù)組的話,我們的 放到哪里比較簡(jiǎn)單呢?dep
回憶一下現(xiàn)在的結(jié)構(gòu)。
const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent);
上邊的代碼執(zhí)行過后會(huì)是下圖的結(jié)構(gòu)。
list
屬性在閉包中擁有了 屬性,通過 ,收集到了包含 函數(shù)的 。Dep
new Watcher
updateCompnent
Watcher
同時(shí)因?yàn)?nbsp;的 是數(shù)組,也就是對(duì)象,通過上篇 list
value
["hello"]
響應(yīng)式系統(tǒng)之深度響應(yīng) (opens new window)我們知道,它也會(huì)去調(diào)用 函數(shù)。Observer
那么,我是不是在 中也加一個(gè) 就可以了。Observer
Dep
這樣當(dāng)我們調(diào)用數(shù)組方法去修改 的值的時(shí)候,去通知 中的 就可以了。['hello']
Observer
Dep
3、收集依賴代碼實(shí)現(xiàn)
按照上邊的思路,完善一下 類。Observer
export class Observer { constructor(value) { /******新增 *************************/ this.dep = new Dep(); /************************************/ this.walk(value); } /** * 遍歷對(duì)象所有的屬性,調(diào)用 defineReactive * 攔截對(duì)象屬性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } }
然后在 中,當(dāng)前 中的 也去收集依賴。get
Oberver
dep
export function defineReactive(obj, key, val, shallow) { const property = Object.getOwnPropertyDescriptor(obj, key); // 讀取用戶可能自己定義了的 get、set const getter = property && property.get; const setter = property && property.set; // val 沒有傳進(jìn)來話進(jìn)行手動(dòng)賦值 if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } const dep = new Dep(); // 持有一個(gè) Dep 對(duì)象,用來保存所有依賴于該變量的 Watcher let childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); /******新增 *************************/ if (childOb) { // 當(dāng)前 value 是數(shù)組,去收集依賴 if (Array.isArray(value)) { childOb.dep.depend(); } } /************************************/ } return value; }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val; if (setter) { setter.call(obj, newVal); } else { val = newVal; } dep.notify(); }, }); }
4、通知依賴代碼實(shí)現(xiàn)
我們已經(jīng)重寫了 方法,但直接覆蓋全局的 方法肯定是不好的,我們可以在 類中去操作,如果當(dāng)前 是數(shù)組,就去攔截它的 方法。array
arrray
Observer
value
array
這里就回到 的原型鏈上了,我們可以通過瀏覽器自帶的 ,將當(dāng)前對(duì)象的原型指向我們重寫過的方法即可。js
__proto__
考慮兼容性的問題,如果 不存在,我們直接將重寫過的方法復(fù)制給當(dāng)前對(duì)象即可。__proto__
import { arrayMethods } from './array' // 上邊重寫的所有數(shù)組方法 /* export const hasProto = "__proto__" in {}; */ export class Observer { constructor(value) { this.dep = new Dep(); /******新增 *************************/ if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } /************************************/ } else { this.walk(value); } } /** * 遍歷對(duì)象所有的屬性,調(diào)用 defineReactive * 攔截對(duì)象屬性的 get 和 set 方法 */ walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } } /** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment(target, src) { /* eslint-disable no-proto */ target.__proto__ = src; /* eslint-enable no-proto */ } /** * Augment a target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ function copyAugment(target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i]; def(target, key, src[key]); } }
還需要考慮一點(diǎn),數(shù)組方法中我們只能拿到 值,那么怎么拿到 對(duì)應(yīng)的 呢。value
value
Observer
我們只需要在 類中,增加一個(gè)屬性來指向自身即可。Observe
export class Observer { constructor(value) { this.dep = new Dep(); /******新增 *************************/ def(value, '__ob__', this) /************************************/ if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } } else { this.walk(value); } } ... }
回到最開始重寫的 方法中,只需要從 中拿到 去通知 即可。array
__ob__
Dep
Watcher
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ import { def } from "./util"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); /*****************這里相當(dāng)于調(diào)用了對(duì)象 set 需要通知 watcher ************************/ const ob = this.__ob__; // notify change ob.dep.notify(); /**************************************************************************** */ return result; }); });
5、測(cè)試
import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"], }; observe(data); const updateComponent = () => { for (const item of data.list) { console.log(item); } }; new Watcher(updateComponent); data.list.push("liang");
這樣當(dāng)調(diào)用 方法的時(shí)候,就會(huì)觸發(fā)相應(yīng)的 來執(zhí)行 函數(shù)了。push
Watcher
updateComponent
當(dāng)前的依賴就變成了下邊的樣子:
6、總結(jié)
對(duì)于數(shù)組的響應(yīng)式我們解決了三個(gè)問題,依賴放在哪里、收集依賴和通知依賴。
我們來和普通對(duì)象屬性進(jìn)行一下對(duì)比。
到此這篇關(guān)于Vue2 響應(yīng)式系統(tǒng)之?dāng)?shù)組的文章就介紹到這了,更多相關(guān)Vue2 數(shù)組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于vue中如何監(jiān)聽數(shù)組變化
這篇文章主要介紹了關(guān)于vue中如何監(jiān)聽數(shù)組變化,對(duì)vue感興趣的同學(xué),必須得參考下2021-04-04vue中如何使用lodash的debounce防抖函數(shù)
防抖函數(shù) debounce 指的是某個(gè)函數(shù)在某段時(shí)間內(nèi),無論觸發(fā)了多少次回調(diào),都只執(zhí)行最后一次,在Vue中使用防抖函數(shù)可以避免在頻繁觸發(fā)的事件中重復(fù)執(zhí)行操作,這篇文章主要介紹了vue中使用lodash的debounce防抖函數(shù),需要的朋友可以參考下2024-01-01vue favicon設(shè)置以及動(dòng)態(tài)修改favicon的方法
這篇文章主要介紹了vue favicon設(shè)置以及動(dòng)態(tài)修改favicon的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12Vscode關(guān)閉Eslint語法檢查的多種方式(保證有效)
eslint是一個(gè)JavaScript的校驗(yàn)插件,通常用來校驗(yàn)語法或代碼的書寫風(fēng)格,下面這篇文章主要給大家介紹了關(guān)于Vscode關(guān)閉Eslint語法檢查的多種方式,文章通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流
本文主要介紹了Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12