從觀察者模式到響應(yīng)式的設(shè)計(jì)原理
響應(yīng)式對(duì)使用過(guò) Vue 或 RxJS 的小伙伴來(lái)說(shuō),應(yīng)該都不會(huì)陌生。響應(yīng)式也是 Vue 的核心功能特性之一,因此如果要想掌握 Vue,我們就必須深刻理解響應(yīng)式。接下來(lái)阿寶哥將從觀察者模式說(shuō)起,然后結(jié)合 observer-util 這個(gè)庫(kù),帶大家一起深入學(xué)習(xí)響應(yīng)式的原理。
一、觀察者模式
觀察者模式,它定義了一種 一對(duì)多 的關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象,這個(gè)主題對(duì)象的狀態(tài)發(fā)生變化時(shí)就會(huì)通知所有的觀察者對(duì)象,使得它們能夠自動(dòng)更新自己。在觀察者模式中有兩個(gè)主要角色:Subject(主題)和 Observer(觀察者)。

由于觀察者模式支持簡(jiǎn)單的廣播通信,當(dāng)消息更新時(shí),會(huì)自動(dòng)通知所有的觀察者。下面我們來(lái)看一下如何使用 TypeScript 來(lái)實(shí)現(xiàn)觀察者模式:
1.1 定義 ConcreteObserver
- interface Observer {
- notify: Function;
- }
- class ConcreteObserver implements Observer{
- constructor(private name: string) {}
- notify() {
- console.log(`${this.name} has been notified.`);
- }
- }
1.2 定義 Subject 類
- class Subject {
- private observers: Observer[] = [];
- public addObserver(observer: Observer): void {
- this.observers.push(observer);
- }
- public notifyObservers(): void {
- console.log("notify all the observers");
- this.observers.forEach(observer => observer.notify());
- }
- }
1.3 使用示例
- // ① 創(chuàng)建主題對(duì)象
- const subject: Subject = new Subject();
- // ② 添加觀察者
- const observerA = new ConcreteObserver("ObserverA");
- const observerC = new ConcreteObserver("ObserverC");
- subject.addObserver(observerA);
- subject.addObserver(observerC);
- // ③ 通知所有觀察者
- subject.notifyObservers();
對(duì)于以上的示例來(lái)說(shuō),主要包含三個(gè)步驟:① 創(chuàng)建主題對(duì)象、② 添加觀察者、③ 通知觀察者。上述代碼成功運(yùn)行后,控制臺(tái)會(huì)輸出以下結(jié)果:
- notify all the observers
- ObserverA has been notified.
- ObserverC has been notified.
在前端大多數(shù)場(chǎng)景中,我們所觀察的目標(biāo)是數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候,頁(yè)面能實(shí)現(xiàn)自動(dòng)的更新,對(duì)應(yīng)的效果如下圖所示:

要實(shí)現(xiàn)自動(dòng)更新,我們需要滿足兩個(gè)條件:一個(gè)是能實(shí)現(xiàn)精準(zhǔn)地更新,另一個(gè)是能檢測(cè)到數(shù)據(jù)的異動(dòng)。要能實(shí)現(xiàn)精準(zhǔn)地更新就需要收集對(duì)該數(shù)據(jù)異動(dòng)感興趣的更新函數(shù)(觀察者),在完成收集之后,當(dāng)檢測(cè)到數(shù)據(jù)異動(dòng),就可以通知對(duì)應(yīng)的更新函數(shù)。
上面的描述看起來(lái)比較繞,其實(shí)要實(shí)現(xiàn)自動(dòng)更新,我們就是要讓 ① 創(chuàng)建主題對(duì)象、② 添加觀察者、③ 通知觀察者 這三個(gè)步驟實(shí)現(xiàn)自動(dòng)化,這就是實(shí)現(xiàn)響應(yīng)式的核心思路。接下來(lái),我們來(lái)舉一個(gè)具體的示例:

相信熟悉 Vue2 響應(yīng)式原理的小伙伴,對(duì)上圖中的代碼都不會(huì)陌生,其中第二步驟也被稱為收集依賴。通過(guò)使用 Object.defineProperty API,我們可以攔截對(duì)數(shù)據(jù)的讀取和修改操作。
若在函數(shù)體中對(duì)某個(gè)數(shù)據(jù)進(jìn)行讀取,則表示此函數(shù)對(duì)該數(shù)據(jù)的異動(dòng)感興趣。當(dāng)進(jìn)行數(shù)據(jù)讀取時(shí),就會(huì)觸發(fā)已定義的 getter 函數(shù),這時(shí)就可以把數(shù)據(jù)的觀察者存儲(chǔ)起來(lái)。而當(dāng)數(shù)據(jù)發(fā)生異動(dòng)的時(shí)候,我們就可以通知觀察者列表中的所有觀察者,從而執(zhí)行相應(yīng)的更新操作。
Vue3 使用了 Proxy API 來(lái)實(shí)現(xiàn)響應(yīng)式,Proxy API 相比 Object.defineProperty API 有哪些優(yōu)點(diǎn)呢?這里阿寶哥不打算展開介紹了,后面打算寫一篇專門的文章來(lái)介紹 Proxy API。下面阿寶哥將開始介紹本文的主角 —— observer-util:
- Transparent reactivity with 100% language coverage. Made with ❤️ and ES6 Proxies.
- https://github.com/nx-js/observer-util
該庫(kù)內(nèi)部也是利用了 ES6 的 Proxy API 來(lái)實(shí)現(xiàn)響應(yīng)式,在介紹它的工作原理前,我們先來(lái)看一下如何使用它。
二、observer-util 簡(jiǎn)介
observer-util 這個(gè)庫(kù)使用起來(lái)也很簡(jiǎn)單,利用該庫(kù)提供的 observable 和 observe 函數(shù),我們就可以方便地實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式。下面我們先來(lái)舉個(gè)簡(jiǎn)單的例子:
2.1 已知屬性
- import { observable, observe } from '@nx-js/observer-util';
- const counter = observable({ num: 0 });
- const countLogger = observe(() => console.log(counter.num)); // 輸出 0
- counter.num++; // 輸出 1
在以上代碼中,我們從 @nx-js/observer-util 模塊中分別導(dǎo)入 observable 和 observe 函數(shù)。其中 observable 函數(shù)用于創(chuàng)建可觀察的對(duì)象,而 observe 函數(shù)用于注冊(cè)觀察者函數(shù)。以上的代碼成功執(zhí)行后,控制臺(tái)會(huì)依次輸出 0 和 1。除了已知屬性外,observer-util 也支持動(dòng)態(tài)屬性。
2.2 動(dòng)態(tài)屬性
- import { observable, observe } from '@nx-js/observer-util';
- const profile = observable();
- observe(() => console.log(profile.name));
- profile.name = 'abao'; // 輸出 'abao'
以上的代碼成功執(zhí)行后,控制臺(tái)會(huì)依次輸出 undefined 和 abao。observer-util 除了支持普通對(duì)象之外,它還支持?jǐn)?shù)組和 ES6 中的集合,比如 Map、Set 等。這里我們以常用的數(shù)組為例,來(lái)看一下如何讓數(shù)組對(duì)象變成響應(yīng)式對(duì)象。
2.3 數(shù)組
- import { observable, observe } from '@nx-js/observer-util';
- const users = observable([]);
- observe(() => console.log(users.join(', ')));
- users.push('abao'); // 輸出 'abao'
- users.push('kakuqo'); // 輸出 'abao, kakuqo'
- users.pop(); // 輸出 'abao,'
這里阿寶哥只介紹了幾個(gè)簡(jiǎn)單的示例,對(duì) observer-util 其他使用示例感興趣的小伙伴,可以閱讀該項(xiàng)目的 README.md 文檔。接下來(lái),阿寶哥將以最簡(jiǎn)單的例子為例,來(lái)分析一下 observer-util 這個(gè)庫(kù)響應(yīng)式的實(shí)現(xiàn)原理。
- 如果你想在本地運(yùn)行以上示例的話,可以先修改 debug/index.js 目錄下的 index.js文件,然后在根目錄下執(zhí)行 npm run debug 命令。
三、observer-util 原理解
析首先,我們?cè)賮?lái)回顧一下最早的那個(gè)例子:
- import { observable, observe } from '@nx-js/observer-util';
- const counter = observable({ num: 0 }); // A
- const countLogger = observe(() => console.log(counter.num)); // B
- counter.num++; // C
在第 A 行中,我們通過(guò) observable 函數(shù)創(chuàng)建了可觀察的 counter 對(duì)象,該對(duì)象的內(nèi)部結(jié)構(gòu)如下:

通過(guò)觀察上圖可知,counter 變量所指向的是一個(gè) Proxy 對(duì)象,該對(duì)象含有 3 個(gè) Internal slots。那么 observable 函數(shù)是如何將我們的 { num: 0 } 對(duì)象轉(zhuǎn)換成 Proxy 對(duì)象呢?在項(xiàng)目的 src/observable.js 文件中,我們找到了該函數(shù)的定義:
- // src/observable.js
- export function observable (obj = {}) {
- // 如果obj已經(jīng)是一個(gè)observable對(duì)象或者不應(yīng)該被包裝,則直接返回它
- if (proxyToRaw.has(obj) || !builtIns.shouldInstrument(obj)) {
- return obj
- }
- // 如果obj已經(jīng)有一個(gè)對(duì)應(yīng)的observable對(duì)象,則將其返回。否則創(chuàng)建一個(gè)新的observable對(duì)象
- return rawToProxy.get(obj) || createObservable(obj)
- }
在以上代碼中出現(xiàn)了 proxyToRaw 和 rawToProxy 兩個(gè)對(duì)象,它們被定義在 src/internals.js 文件中:
- // src/internals.js
- export const proxyToRaw = new WeakMap()
- export const rawToProxy = new WeakMap()
這兩個(gè)對(duì)象分別存儲(chǔ)了 proxy => raw 和 raw => proxy 之間的映射關(guān)系,其中 raw 表示原始對(duì)象,proxy 表示包裝后的 Proxy 對(duì)象。很明顯首次執(zhí)行時(shí),proxyToRaw.has(obj) 和 rawToProxy.get(obj) 分別會(huì)返回 false 和 undefined,所以會(huì)執(zhí)行 || 運(yùn)算符右側(cè)的邏輯。
下面我們來(lái)分析一下 shouldInstrument 函數(shù),該函數(shù)的定義如下:
- // src/builtIns/index.js
- export function shouldInstrument ({ constructor }) {
- const isBuiltIn =
- typeof constructor === 'function' &&
- constructor.name in globalObj &&
- globalObj[constructor.name] === constructor
- return !isBuiltIn || handlers.has(constructor)
- }
在 shouldInstrument 函數(shù)內(nèi)部,會(huì)使用參數(shù) obj 的構(gòu)造函數(shù)判斷其是否為內(nèi)置對(duì)象,對(duì)于 { num: 0 } 對(duì)象來(lái)說(shuō),它的構(gòu)造函數(shù)是 ƒ Object() { [native code] },因此 isBuiltIn 的值為 true,所以會(huì)繼續(xù)執(zhí)行 || 運(yùn)算符右側(cè)的邏輯。其中 handlers 對(duì)象是一個(gè) Map 對(duì)象:
- // src/builtIns/index.js
- const handlers = new Map([
- [Map, collectionHandlers],
- [Set, collectionHandlers],
- [WeakMap, collectionHandlers],
- [WeakSet, collectionHandlers],
- [Object, false],
- [Array, false],
- [Int8Array, false],
- [Uint8Array, false],
- // 省略部分代碼
- [Float64Array, false]
- ])
看完 handlers 的結(jié)構(gòu),很明顯 !builtIns.shouldInstrument(obj) 表達(dá)式的結(jié)果為 false。所以接下來(lái),我們的焦點(diǎn)就是 createObservable 函數(shù):
- function createObservable (obj) {
- const handlers = builtIns.getHandlers(obj) || baseHandlers
- const observable = new Proxy(obj, handlers)
- // 保存raw => proxy,proxy => raw 之間的映射關(guān)系
- rawToProxy.set(obj, observable)
- proxyToRaw.set(observable, obj)
- storeObservable(obj)
- return observable
- }
通過(guò)觀察以上代碼,我們就知道了為什么調(diào)用 observable({ num: 0 }) 函數(shù)之后,返回的是一個(gè) Proxy 對(duì)象。對(duì)于 Proxy 的構(gòu)造函數(shù)來(lái)說(shuō),它支持兩個(gè)參數(shù):
- const p = new Proxy(target, handler)
- target:要使用 Proxy 包裝的目標(biāo)對(duì)象(可以是任何類型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理);
- handler:一個(gè)通常以函數(shù)作為屬性的對(duì)象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 p 的行為。
示例中的 target 指向的就是 { num: 0 } 對(duì)象,而 handlers 的值會(huì)根據(jù) obj 的類型而返回不同的 handlers:
- // src/builtIns/index.js
- export function getHandlers (obj) {
- return handlers.get(obj.constructor) // [Object, false],
- }
而 baseHandlers 是一個(gè)包含了 get、has 和 set 等 “陷阱“ 的對(duì)象:
- export default { get, has, ownKeys, set, deleteProperty }
在創(chuàng)建完 observable 對(duì)象之后,會(huì)保存 raw => proxy,proxy => raw 之間的映射關(guān)系,然后再調(diào)用 storeObservable 函數(shù)執(zhí)行存儲(chǔ)操作,storeObservable 函數(shù)被定義在 src/store.js 文件中:
- // src/store.js
- const connectionStore = new WeakMap()
- export function storeObservable (obj) {
- // 用于后續(xù)保存obj.key -> reaction之間映射關(guān)系
- connectionStore.set(obj, new Map())
- }
介紹了那么多,阿寶哥用一張圖來(lái)總結(jié)一下前面的內(nèi)容:

至于 proxyToRaw 和 rawToProxy 對(duì)象有什么用呢?相信看完以下代碼,你就會(huì)知道答案。
- // src/observable.js
- export function observable (obj = {}) {
- // 如果obj已經(jīng)是一個(gè)observable對(duì)象或者不應(yīng)該被包裝,則直接返回它
- if (proxyToRaw.has(obj) || !builtIns.shouldInstrument(obj)) {
- return obj
- }
- // 如果obj已經(jīng)有一個(gè)對(duì)應(yīng)的observable對(duì)象,則將其返回。否則創(chuàng)建一個(gè)新的observable對(duì)象
- return rawToProxy.get(obj) || createObservable(obj)
- }
下面我們來(lái)開始分析第 B 行:
- const countLogger = observe(() => console.log(counter.num)); // B
observe 函數(shù)被定義在 src/observer.js 文件中,其具體定義如下:
- // src/observer.js
- export function observe (fn, options = {}) {
- // const IS_REACTION = Symbol('is reaction')
- const reaction = fn[IS_REACTION]
- ? fn
- : function reaction () {
- return runAsReaction(reaction, fn, this, arguments)
- }
- // 省略部分代碼
- reaction[IS_REACTION] = true
- // 如果非lazy,則直接運(yùn)行
- if (!options.lazy) {
- reaction()
- }
- return reaction
- }
在上面代碼中,會(huì)先判斷傳入的 fn 是不是 reaction 函數(shù),如果是的話,直接使用它。如果不是的話,會(huì)把傳入的 fn 包裝成 reaction 函數(shù),然后再調(diào)用該函數(shù)。在 reaction 函數(shù)內(nèi)部,會(huì)調(diào)用另一個(gè)函數(shù) —— runAsReaction,顧名思義該函數(shù)用于運(yùn)行 reaction 函數(shù)。
runAsReaction 函數(shù)被定義在 src/reactionRunner.js 文件中:
- // src/reactionRunner.js
- const reactionStack = []
- export function runAsReaction (reaction, fn, context, args) {
- // 省略部分代碼
- if (reactionStack.indexOf(reaction) === -1) {
- // 釋放(obj -> key -> reactions) 鏈接并復(fù)位清理器鏈接
- releaseReaction(reaction)
- try {
- // 壓入到reactionStack堆棧中,以便于在get陷阱中能建立(observable.prop -> reaction)之間的聯(lián)系
- reactionStack.push(reaction)
- return Reflect.apply(fn, context, args)
- } finally {
- // 從reactionStack堆棧中,移除已執(zhí)行的reaction函數(shù)
- reactionStack.pop()
- }
- }
- }
在 runAsReaction 函數(shù)體中,會(huì)把當(dāng)前正在執(zhí)行的 reaction 函數(shù)壓入 reactionStack棧中,然后使用 Reflect.apply API 調(diào)用傳入的 fn 函數(shù)。當(dāng) fn 函數(shù)執(zhí)行時(shí),就是執(zhí)行 console.log(counter.num) 語(yǔ)句,在該語(yǔ)句內(nèi),會(huì)訪問(wèn) counter 對(duì)象的 num 屬性。counter 對(duì)象是一個(gè) Proxy 對(duì)象,當(dāng)訪問(wèn)該對(duì)象的屬性時(shí),會(huì)觸發(fā) baseHandlers 中 get 陷阱:
- // src/handlers.js
- function get (target, key, receiver) {
- const result = Reflect.get(target, key, receiver)
- // 注冊(cè)并保存(observable.prop -> runningReaction)
- registerRunningReactionForOperation({ target, key, receiver, type: 'get' })
- const observableResult = rawToProxy.get(result)
- if (hasRunningReaction() && typeof result === 'object' && result !== null) {
- // 省略部分代碼
- }
- return observableResult || result
- }
在以上的函數(shù)中,registerRunningReactionForOperation 函數(shù)用于保存 observable.prop -> runningReaction 之間的映射關(guān)系。其實(shí)就是為對(duì)象的指定屬性,添加對(duì)應(yīng)的觀察者,這是很關(guān)鍵的一步。所以我們來(lái)重點(diǎn)分析 registerRunningReactionForOperation 函數(shù):
- // src/reactionRunner.js
- export function registerRunningReactionForOperation (operation) {
- // 從棧頂獲取當(dāng)前正在執(zhí)行的reaction
- const runningReaction = reactionStack[reactionStack.length - 1]
- if (runningReaction) {
- debugOperation(runningReaction, operation)
- registerReactionForOperation(runningReaction, operation)
- }
- }
在 registerRunningReactionForOperation 函數(shù)中,首先會(huì)從 reactionStack 堆棧中獲取正在運(yùn)行的 reaction 函數(shù),然后再次調(diào)用 registerReactionForOperation 函數(shù)為當(dāng)前的操作注冊(cè) reaction 函數(shù),具體的處理邏輯如下所示:
- // src/store.js
- export function registerReactionForOperation (reaction, { target, key, type }) {
- // 省略部分代碼
- const reactionsForObj = connectionStore.get(target) // A
- let reactionsForKey = reactionsForObj.get(key) // B
- if (!reactionsForKey) { // C
- reactionsForKey = new Set()
- reactionsForObj.set(key, reactionsForKey)
- }
- if (!reactionsForKey.has(reaction)) { // D
- reactionsForKey.add(reaction)
- reaction.cleaners.push(reactionsForKey)
- }
- }
在調(diào)用 observable(obj) 函數(shù)創(chuàng)建可觀察對(duì)象時(shí),會(huì)為以 obj 對(duì)象為 key,保存在 connectionStore (connectionStore.set(obj, new Map()) )對(duì)象中。
阿寶哥把 registerReactionForOperation 函數(shù)內(nèi)部的處理邏輯分為 4 個(gè)部分:
- (A):從 connectionStore (WeakMap)對(duì)象中獲取 target 對(duì)應(yīng)的值,會(huì)返回一個(gè) reactionsForObj(Map)對(duì)象;
- (B):從 reactionsForKey (Map)對(duì)象中獲取 key(對(duì)象屬性)對(duì)應(yīng)的值,如果不存在的話,會(huì)返回 undefined;
- (C):如果 reactionsForKey 為 undefined,則會(huì)創(chuàng)建一個(gè) Set 對(duì)象,并把該對(duì)象作為 value,保存在 reactionsForObj(Map)對(duì)象中;
- (D):判斷 reactionsForKey(Set)集合中是否含有當(dāng)前的 reaction 函數(shù),如果不存在的話,把當(dāng)前的 reaction 函數(shù)添加到 reactionsForKey(Set)集合中。
為了讓大家能夠更好地理解該部分的內(nèi)容,阿寶哥繼續(xù)通過(guò)畫圖來(lái)總結(jié)上述的內(nèi)容:

因?yàn)閷?duì)象中的每個(gè)屬性都可以關(guān)聯(lián)多個(gè) reaction 函數(shù),為了避免出現(xiàn)重復(fù),我們使用 Set 對(duì)象來(lái)存儲(chǔ)每個(gè)屬性所關(guān)聯(lián)的 reaction 函數(shù)。而一個(gè)對(duì)象又可以包含多個(gè)屬性,所以 observer-util 內(nèi)部使用了 Map 對(duì)象來(lái)存儲(chǔ)每個(gè)屬性與 reaction 函數(shù)之間的關(guān)聯(lián)關(guān)系。
此外,為了支持能把多個(gè)對(duì)象變成 observable 對(duì)象并在原始對(duì)象被銷毀時(shí)能及時(shí)地回收內(nèi)存, observer-util 定義了 WeakMap 類型的 connectionStore 對(duì)象來(lái)存儲(chǔ)對(duì)象的鏈接關(guān)系。對(duì)于當(dāng)前的示例,connectionStore 對(duì)象的內(nèi)部結(jié)構(gòu)如下所示:

最后,我們來(lái)分析 counter.num++; 這行代碼。簡(jiǎn)單起見,阿寶哥只分析核心的處理邏輯,對(duì)完整代碼感興趣的小伙伴,可以閱讀該項(xiàng)目的源碼。當(dāng)執(zhí)行 counter.num++; 這行代碼時(shí),會(huì)觸發(fā)已設(shè)置的 set 陷阱:
- // src/handlers.js
- function set (target, key, value, receiver) {
- // 省略部分代碼
- const hadKey = hasOwnProperty.call(target, key)
- const oldValue = target[key]
- const result = Reflect.set(target, key, value, receiver)
- if (!hadKey) {
- queueReactionsForOperation({ target, key, value, receiver, type: 'add' })
- } else if (value !== oldValue) {
- queueReactionsForOperation({
- target,
- key,
- value,
- oldValue,
- receiver,
- type: 'set'
- })
- }
- return result
- }
對(duì)于我們的示例,將會(huì)調(diào)用 queueReactionsForOperation 函數(shù):
- // src/reactionRunner.js
- export function queueReactionsForOperation (operation) {
- // iterate and queue every reaction, which is triggered by obj.key mutation
- getReactionsForOperation(operation).forEach(queueReaction, operation)
- }
在 queueReactionsForOperation 函數(shù)內(nèi)部會(huì)繼續(xù)調(diào)用 getReactionsForOperation 函數(shù)獲取當(dāng)前 key 對(duì)應(yīng)的 reactions:
- // src/store.js
- export function getReactionsForOperation ({ target, key, type }) {
- const reactionsForTarget = connectionStore.get(target)
- const reactionsForKey = new Set()
- if (type === 'clear') {
- reactionsForTarget.forEach((_, key) => {
- addReactionsForKey(reactionsForKey, reactionsForTarget, key)
- })
- } else {
- addReactionsForKey(reactionsForKey, reactionsForTarget, key)
- }
- // 省略部分代碼
- return reactionsForKey
- }
在成功獲取當(dāng)前 key 對(duì)應(yīng)的 reactions 對(duì)象之后,會(huì)遍歷該對(duì)象執(zhí)行每個(gè) reaction,具體的處理邏輯被定義在 queueReaction 函數(shù)中:
- // src/reactionRunner.js
- function queueReaction (reaction) {
- debugOperation(reaction, this)
- // queue the reaction for later execution or run it immediately
- if (typeof reaction.scheduler === 'function') {
- reaction.scheduler(reaction)
- } else if (typeof reaction.scheduler === 'object') {
- reaction.scheduler.add(reaction)
- } else {
- reaction()
- }
- }
因?yàn)槲覀兊氖纠](méi)有配置 scheduler 參數(shù),所以就會(huì)直接執(zhí)行 else 分支的代碼,即執(zhí)行 reaction() 該語(yǔ)句。
好的,observer-util 這個(gè)庫(kù)內(nèi)部如何把普通對(duì)象轉(zhuǎn)換為可觀察對(duì)象的核心邏輯已經(jīng)分析完了。對(duì)于普通對(duì)象來(lái)說(shuō),observer-util 內(nèi)部通過(guò) Proxy API 提供 get 和 set 陷阱,實(shí)現(xiàn)自動(dòng)添加觀察者(添加 reaction 函數(shù))和通知觀察者(執(zhí)行 reaction 函數(shù))的處理邏輯。
如果你看完本文所介紹的內(nèi)容,應(yīng)該就可以理解 Vue3 中 reactivity 模塊內(nèi) targetMap 的相關(guān)定義:
- // vue-next/packages/reactivity/src/effect.ts
- type Dep = Set<ReactiveEffect>
- type KeyToDepMap = Map<any, Dep>
- const targetMap = new WeakMap<any, KeyToDepMap>()
除了普通對(duì)象和數(shù)組之外,observer-util 還支持 ES6 中的集合,比如 Map、Set 和 WeakMap 等。當(dāng)處理這些對(duì)象時(shí),在創(chuàng)建 Proxy 對(duì)象時(shí),會(huì)使用 collectionHandlers對(duì)象,而不是 baseHandlers 對(duì)象。這部分內(nèi)容,阿寶哥就不再展開介紹,感興趣的小伙伴可以自行閱讀相關(guān)代碼。如果想了解 WeakMap 的相關(guān)知識(shí),可以閱讀 你不知道的 WeakMap 這篇文章。
四、參考資源
- what-is-an-internal-slot-of-an-object-in-javascript
- MDN-Proxy
- MDN-Reflect