你不知道的 Proxy
阿寶哥將從 6 個方面入手,帶你一步一步揭開 Proxy 對象的神祕面紗。閱讀完本文,你將瞭解以下內容:
-
代理的作用;
-
Proxy 對象與 Reflect 對象的相關知識;
-
Proxy 對象的 6 個使用場景;
-
使用 Proxy 對象時的一些注意事項;
-
Proxy 在開源項目中的應用。
一、聊一聊代理
在日常工作中,相信挺多小夥伴都用過 Web 調試代理工具,比如 Fiddler 或 Charles。通過使用 Web 調試代理工具,我們可以抓取 HTTP/HTTPS 協議請求,還可以手動修改請求參數和響應結果。不僅如此,在調試線上問題時,利用 Web 調試代理工具,你還可以把線上 壓縮混淆過 的 JS 文件映射成本地 未壓縮混淆過 的 JS 文件。
在簡單介紹了 Web 調試代理工具的基本功能之後,我們來看一下使用 Web 調試代理工具的 HTTP 請求流程:
通過上圖可知,在引入 Web 調試代理工具之後,我們發起的 HTTP 請求都會通過 Web Proxy 進行轉發和處理。增加了 Web Proxy 代理層,讓我們能夠更好地控制 HTTP 請求的流程。對於單頁應用程序來說,當從服務器獲取數據之後,我們就會讀取相應的數據在頁面上顯示出來:
以上流程與瀏覽器直接從服務器獲取數據類似:
爲了能夠靈活控制 HTTP 請求的流程,我們增加了的 Web Proxy 層。那麼我們能否控制數據對象的讀取流程呢?答案是可以的,我們可以利用 Web API,比如 Object.defineProperty 或 Proxy API。在引入 Web API 之後,數據的訪問流程如下圖所示:
接下來,阿寶哥將重點介紹 Proxy API,它可是 Vue3 實現數據響應式幕後的 “功臣” 喲。對它感興趣的小夥伴,跟阿寶哥一起學起來吧。
二、Proxy 對象簡介
Proxy 對象用於創建一個對象的代理,從而實現基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數調用等)。Proxy 的構造函數語法爲:
1const p = new Proxy(target, handler) 2 3
相關的參數說明如下:
-
target:要使用
Proxy
包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)。 -
handler:一個通常以函數作爲屬性的對象,各屬性中的函數分別定義了在執行各種操作時代理
p
的行爲。
在介紹 Proxy 對象的使用示例前,我們先來了解一下它的兼容性:
(圖片來源:https://caniuse.com/?search=Proxy)
由上圖可知,Proxy API 的兼容性並不是很好,所以大家在使用的時候要注意其兼容性問題。
2.1 Proxy 對象使用示例
瞭解完 Proxy 構造函數,我們來看一個簡單的例子:
1const man = {
2 name: "阿寶哥",
3};
4
5const proxy = new Proxy(man, {
6 get(target, property, receiver) {
7 console.log(`正在訪問${property}屬性`);
8 return target[property];
9 },
10});
11
12console.log(proxy.name);
13console.log(proxy.age);
14
15
在以上示例中,我們使用了 Proxy 構造函數爲 man
對象,創建了一個代理對象。在創建代理對象時,我們定義了一個 get 捕獲器,用於捕獲屬性讀取的操作。 捕獲器的作用就是用於攔截用戶對目標對象的相關操作,在這些操作傳播到目標對象之前,會先調用對應的捕獲器函數,從而攔截並修改相應的行爲。
在設置了 get 捕獲器之後,當成功運行以上的示例代碼,控制檯會輸出以下結果:
1正在訪問name屬性
2阿寶哥
3正在訪問age屬性
4undefined
5
6
通過觀察以上輸出結果,我們可以發現 get 捕獲器 不僅可以攔截已知屬性的讀取操作,也可以攔截未知屬性的讀取操作。在創建 Proxy 對象時,除了定義 get 捕獲器 之外,我們還可以定義其他的捕獲器,比如 has、set、delete、apply 或 ownKeys 等。
2.2 handler 對象支持的捕獲器
handler 對象支持 13 種捕獲器,這裏阿寶哥只列舉以下 5 種常用的捕獲器:
-
handler.get():屬性讀取操作的捕獲器。
-
handler.set():屬性設置操作的捕獲器。
-
handler.deleteProperty():delete 操作符的捕獲器。
-
handler.ownKeys():Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕獲器。
-
handler.has():in 操作符的捕獲器。
需要注意的是,所有的捕獲器是可選的。如果沒有定義某個捕獲器,那麼就會保留源對象的默認行爲。 看完上面的捕獲器介紹,是不是覺得 Proxy 對象很強大。
三、Reflect 對象簡介
Reflect 是一個內置的對象,它提供攔截 JavaScript 操作的方法。這些方法與 proxy handlers 的方法相同。Reflect 不是一個函數對象,因此它是不可構造的。
在介紹 Reflect 對象的使用示例前,我們先來了解一下它的兼容性:
(圖片來源:https://caniuse.com/?search=Reflect)
3.1 Reflect 對象使用示例
1const man = {
2 name: "阿寶哥",
3 city: "Xiamen",
4};
5
6console.log(Reflect.set(man, "sex", 1)); // true
7console.log(Reflect.has(man, "name")); // true
8console.log(Reflect.has(man, "age")); // false
9console.log(Reflect.ownKeys(man)); // [ 'name', 'city', 'sex' ]
10
11
除了示例中介紹的 set
、has
和 ownKeys
方法之外,Reflect 對象還支持 get
、defineProperty
和 deleteProperty
等方法。下面阿寶哥將簡單介紹 Reflect
對象所支持的一些靜態方法。
3.2 Reflect 對象支持的靜態方法
Reflect 的所有屬性和方法都是靜態的,該對象提供了與 Proxy handler 對象相關的 13 個方法。同樣,這裏阿寶哥只列舉以下 5 個常用的方法:
-
Reflect.get(target, propertyKey[, receiver]):獲取對象身上某個屬性的值,類似於 target[name]。
-
Reflect.set(target, propertyKey, value[, receiver]):將值賦值給屬性的函數。返回一個布爾值,如果更新成功,則返回 true。
-
Reflect.deleteProperty(target, propertyKey):刪除 target 對象的指定屬性,相當於執行 delete target[name]。
-
Reflect.has(target, propertyKey):判斷一個對象是否存在某個屬性,和 in 運算符的功能完全相同。
-
Reflect.ownKeys(target):返回一個包含所有自身屬性(不包含繼承屬性)的數組。
在實際的 Proxy 使用場景中,我們往往會結合 Reflect 對象提供的靜態方法來實現某些特定的功能。爲了讓大家能夠更好地理解並掌握 Proxy 對象,接下來的環節,阿寶哥將列舉 Proxy 對象的 6 個使用場景。
四、Proxy 使用場景
這裏我們先來介紹 Proxy 對象的第一個使用場景 —— 增強型數組。
4.1 增強型數組
定義 enhancedArray 函數
1function enhancedArray(arr) {
2 return new Proxy(arr, {
3 get(target, property, receiver) {
4 const range = getRange(property);
5 const indices = range ? range : getIndices(property);
6 const values = indices.map(function (index) {
7 const key = index < 0 ? String(target.length + index) : index;
8 return Reflect.get(target, key, receiver);
9 });
10 return values.length === 1 ? values[0] : values;
11 },
12 });
13
14 function getRange(str) {
15 var [start, end] = str.split(":").map(Number);
16 if (typeof end === "undefined") return false;
17
18 let range = [];
19 for (let i = start; i < end; i++) {
20 range = range.concat(i);
21 }
22 return range;
23 }
24
25 function getIndices(str) {
26 return str.split(",").map(Number);
27 }
28}
29
30
使用 enhancedArray 函數
1const arr = enhancedArray([1, 2, 3, 4, 5]);
2
3console.log(arr[-1]); //=> 5
4console.log(arr[[2, 4]]); //=> [ 3, 5 ]
5console.log(arr[[2, -2, 1]]); //=> [ 3, 4, 2 ]
6console.log(arr["2:4"]); //=> [ 3, 4]
7console.log(arr["-2:3"]); //=> [ 4, 5, 1, 2, 3 ]
8
9
由以上的輸出結果可知,增強後的數組對象,就可以支持負數索引、分片索引等功能。除了可以增強數組之外,我們也可以使用 Proxy API 來增強普通對象。
4.2 增強型對象
創建 enhancedObject 函數
1const enhancedObject = (target) =>
2 new Proxy(target, {
3 get(target, property) {
4 if (property in target) {
5 return target[property];
6 } else {
7 return searchFor(property, target);
8 }
9 },
10 });
11
12let value = null;
13function searchFor(property, target) {
14 for (const key of Object.keys(target)) {
15 if (typeof target[key] === "object") {
16 searchFor(property, target[key]);
17 } else if (typeof target[property] !== "undefined") {
18 value = target[property];
19 break;
20 }
21 }
22 return value;
23}
24
25
使用 enhancedObject 函數
1const data = enhancedObject({
2 user: {
3 name: "阿寶哥",
4 settings: {
5 theme: "dark",
6 },
7 },
8});
9
10console.log(data.user.settings.theme); // dark
11console.log(data.theme); // dark
12
13
以上代碼運行後,控制檯會輸出以下代碼:
1dark
2dark
3
4
通過觀察以上的輸出結果可知,使用 enhancedObject
函數處理過的對象,我們就可以方便地訪問普通對象內部的深層屬性。
4.3 創建只讀的對象
創建 Proxy 對象
1const man = {
2 name: "semlinker",
3};
4
5const handler = {
6 set: "Read-Only",
7 defineProperty: "Read-Only",
8 deleteProperty: "Read-Only",
9 preventExtensions: "Read-Only",
10 setPrototypeOf: "Read-Only",
11};
12
13const proxy = new Proxy(man, handler);
14
15
使用 proxy 對象
1console.log(proxy.name);
2proxy.name = "kakuqo";
3
4
以上代碼運行後,控制檯會輸出以下代碼:
1semlinker
2proxy.name = "kakuqo";
3 ^
4TypeError: 'Read-Only' returned for property 'set' of object '#<Object>' is not a function
5
6
觀察以上的異常信息可知,導致異常的原因是因爲 handler
對象的 set
屬性值不是一個函數。如果不希望拋出運行時異常,我們可以定義一個 freeze
函數:
1function freeze (obj) {
2 return new Proxy(obj, {
3 set () { return true; },
4 deleteProperty () { return false; },
5 defineProperty () { return true; },
6 setPrototypeOf () { return true; }
7 });
8}
9
10
定義好 freeze
函數,我們使用數組對象來測試一下它的功能:
1let frozen = freeze([1, 2, 3]);
2frozen[0] = 6;
3delete frozen[0];
4frozen = Object.defineProperty(frozen, 0, { value: 66 });
5console.log(frozen); // [ 1, 2, 3 ]
6
7
上述代碼成功執行後,控制檯會輸出 [ 1, 2, 3 ]
,很明顯經過 freeze
函數處理過的數組對象,已經被 “凍結” 了。
4.4 攔截方法調用
定義 traceMethodCalls 函數
1function traceMethodCalls(obj) {
2 const handler = {
3 get(target, propKey, receiver) {
4 const origMethod = target[propKey]; // 獲取原始方法
5 return function (...args) {
6 const result = origMethod.apply(this, args);
7 console.log(
8 propKey + JSON.stringify(args) + " -> " + JSON.stringify(result)
9 );
10 return result;
11 };
12 },
13 };
14 return new Proxy(obj, handler);
15}
16
17
使用 traceMethodCalls 函數
1const obj = {
2 multiply(x, y) {
3 return x * y;
4 },
5};
6
7const tracedObj = traceMethodCalls(obj);
8tracedObj.multiply(2, 5); // multiply[2,5] -> 10
9
10
上述代碼成功執行後,控制檯會輸出 multiply[2,5] -> 10
,即我們能夠成功跟蹤 obj
對象中方法的調用過程。其實,除了能夠跟蹤方法的調用,我們也可以跟蹤對象中屬性的訪問,具體示例如下:
1function tracePropAccess(obj, propKeys) {
2 const propKeySet = new Set(propKeys);
3 return new Proxy(obj, {
4 get(target, propKey, receiver) {
5 if (propKeySet.has(propKey)) {
6 console.log("GET " + propKey);
7 }
8 return Reflect.get(target, propKey, receiver);
9 },
10 set(target, propKey, value, receiver) {
11 if (propKeySet.has(propKey)) {
12 console.log("SET " + propKey + "=" + value);
13 }
14 return Reflect.set(target, propKey, value, receiver);
15 },
16 });
17}
18
19const man = {
20 name: "semlinker",
21};
22const tracedMan = tracePropAccess(man, ["name"]);
23
24console.log(tracedMan.name); // GET name; semlinker
25console.log(tracedMan.age); // undefined
26tracedMan.name = "kakuqo"; // SET name=kakuqo
27
28
在以上示例中,我們定義了一個 tracePropAccess
函數,該函數接收兩個參數:obj 和 propKeys,它們分別表示需跟蹤的目標和需跟蹤的屬性列表。調用 tracePropAccess
函數後,會返回一個代理對象,當我們訪問被跟蹤的屬性時,控制檯就會輸出相應的訪問日誌。
4.5 隱藏屬性
創建 hideProperty 函數
1const hideProperty = (target, prefix = "_") =>
2 new Proxy(target, {
3 has: (obj, prop) => !prop.startsWith(prefix) && prop in obj,
4 ownKeys: (obj) =>
5 Reflect.ownKeys(obj).filter(
6 (prop) => typeof prop !== "string" || !prop.startsWith(prefix)
7 ),
8 get: (obj, prop, rec) => (prop in rec ? obj[prop] : undefined),
9 });
10
11
使用 hideProperty 函數
1const man = hideProperty({
2 name: "阿寶哥",
3 _pwd: "www.semlinker.com",
4});
5
6console.log(man._pwd); // undefined
7console.log('_pwd' in man); // false
8console.log(Object.keys(man)); // [ 'name' ]
9
10
通過觀察以上的輸出結果,我們可以知道,利用 Proxy API,我們實現了指定前綴屬性的隱藏。除了能實現隱藏屬性之外,利用 Proxy API,我們還可以實現驗證屬性值的功能。
4.6 驗證屬性值
創建 validatedUser 函數
1const validatedUser = (target) =>
2 new Proxy(target, {
3 set(target, property, value) {
4 switch (property) {
5 case "email":
6 const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
7 if (!regex.test(value)) {
8 console.error("The user must have a valid email");
9 return false;
10 }
11 break;
12 case "age":
13 if (value < 20 || value > 80) {
14 console.error("A user's age must be between 20 and 80");
15 return false;
16 }
17 break;
18 }
19
20 return Reflect.set(...arguments);
21 },
22 });
23
24
使用 validatedUser 函數
1let user = {
2 email: "",
3 age: 0,
4};
5
6user = validatedUser(user);
7user.email = "semlinker.com"; // The user must have a valid email
8user.age = 100; // A user's age must be between 20 and 80
9
10
上述代碼成功執行後,控制檯會輸出以下結果:
1The user must have a valid email
2A user's age must be between 20 and 80
3
4
介紹完 Proxy 對象的使用場景之後,我們來繼續介紹與 Proxy 對象相關的一些問題。
五、Proxy 相關問題
5.1 this 的指向問題
1const target = {
2 foo() {
3 return {
4 thisIsTarget: this === target,
5 thisIsProxy: this === proxy,
6 };
7 },
8};
9
10const handler = {};
11const proxy = new Proxy(target, handler);
12console.log(target.foo()); // { thisIsTarget: true, thisIsProxy: false }
13console.log(proxy.foo()); // { thisIsTarget: false, thisIsProxy: true }
14
15
上述代碼成功執行後,控制檯會輸出以下結果:
1{ thisIsTarget: true, thisIsProxy: false }
2{ thisIsTarget: false, thisIsProxy: true }
3
4
通過以上輸出的結果,foo
方法中的 this 指向與當前的調用者有關。看起來挺簡單的,但在一些場景下如果稍不注意的話,就會出現問題,比如以下這個示例:
1const _name = new WeakMap();
2
3class Person {
4 constructor(name) {
5 _name.set(this, name);
6 }
7
8 get name() {
9 return _name.get(this);
10 }
11}
12
13
在以上示例中,我們使用 WeakMap
對象來存儲 Person 對象的私有信息。定義完 Person
類,我們就可以通過以下方式來使用它:
1const man = new Person("阿寶哥");
2console.log(man.name); // 阿寶哥
3
4const proxy = new Proxy(man, {});
5console.log(proxy.name); // undefined
6
7
對於以上的代碼,當我們通過 proxy
對象來訪問 name
屬性時,你會發現輸出的結果是 undefined
。這是因爲當使用 proxy.name
的方式訪問 name
屬性時,this
指向的是 proxy
對象,而 _name
WeakMap 對象中存儲的是 man
對象,所以輸出的結果是 undefined
。
然而,對於以上的問題,如果我們按照以下方式定義 Person
類,就不會出現以上問題:
1class Person {
2 constructor(name) {
3 this._name = name;
4 }
5 get name() {
6 return this._name;
7 }
8}
9
10const man = new Person("阿寶哥");
11console.log(man.name); // 阿寶哥
12
13const proxy = new Proxy(man, {});
14console.log(proxy.name); // 阿寶哥
15
16
另外,如果你對 WeakMap 感興趣的話,可以閱讀 你不知道的 WeakMap 這篇文章。
5.2 get 捕獲器 receiver 參數是什麼
1const p = new Proxy(target, {
2 get: function(target, property, receiver) {
3 // receiver
4 }
5});
6
7
get 捕獲器用於攔截對象的讀取屬性操作,該捕獲器含有三個參數:
-
target:目標對象。
-
property:被讀取的屬性名。
-
receiver:指向當前的 Proxy 對象或者繼承於當前 Proxy 的對象。
爲了更好地瞭解 receiver
參數的描述信息,我們來舉個具體的示例:
1const proxy = new Proxy({},
2 {
3 get: function (target, property, receiver) {
4 return receiver;
5 },
6 }
7);
8
9console.dir(proxy.getReceiver === proxy); // true
10var inherits = Object.create(proxy);
11console.dir(inherits.getReceiver === inherits); // true
12
13
那麼我們能否改變 receiver
指向的對象呢?答案是可以的,通過 Reflect 對象提供的 get
方法,我們可以動態設置 receiver
對象的值,具體使用方式如下所示:
1console.dir(Reflect.get(proxy, "getReceiver", "阿寶哥"));
2
3
其實 receiver
的名稱是來源於 ECMAScript 規範:
-
[[Get]] (propertyKey, Receiver) → anyReturn the value of the property whose key is propertyKey from this object. If any ECMAScript code must be executed to retrieve the property value, Receiver is used as the this value when evaluating the code.
-
[[Set]] (propertyKey, value, Receiver) → BooleanSet the value of the property whose key is propertyKey to value. If any ECMAScript code must be executed to set the property value, Receiver is used as the this value when evaluating the code. Returns true if the property value was set or false if it could not be set.
以上的 [[Get]]
和 [[Set]]
被稱爲內部方法,ECMAScript 引擎中的每個對象都與一組內部方法相關聯,這些內部方法定義了其運行時行爲。
需要注意的是,這些內部方法不是 ECMAScript 語言的一部分。對於對象的訪問器屬性來說,在執行內部代碼時,Receiver
將被作爲 this
的值,同樣使用 Reflect 對象提供的 API,我們也可以通過設置 receiver
參數的值來改變 this
的值:
1const obj = {
2 get foo() {
3 return this.bar;
4 },
5};
6
7console.log(Reflect.get(obj, "foo")); // undefined
8console.log(Reflect.get(obj, "foo", { bar: 2021 })); // 2021
9
10
5.3 包裝內置構造函數的實例
當使用 Proxy 包裝內置構造函數實例的時候,可能會出現一些問題。比如使用 Proxy 代理 Date 構造函數的實例:
1const target = new Date();
2const handler = {};
3const proxy = new Proxy(target, handler);
4
5proxy.getDate(); // Error
6
7
當以上代碼運行後,控制檯會輸出以下異常信息:
1proxy.getDate();
2 ^
3TypeError: this is not a Date object.
4
5
出現以上問題的原因是因爲有些原生對象的內部屬性,只有通過正確的 this
才能拿到,所以 Proxy 無法代理這些原生對象的屬性。那麼如何解決這個問題呢?要解決這個問題,我們可以爲 getDate
方法綁定正確的 this
:
1const target = new Date();
2const handler = {
3 get(target, property, receiver) {
4 if (property === "getDate") {
5 return target.getDate.bind(target);
6 }
7 return Reflect.get(target, property, receiver);
8 },
9};
10
11const proxy = new Proxy(target, handler);
12console.log(proxy.getDate());
13
14
5.4 創建可撤銷的代理對象
通過 Proxy.revocable()
方法可以用來創建一個可撤銷的代理對象,該方法的簽名爲:
1Proxy.revocable(target, handler); 2 3
相關的參數說明如下:
-
target:將用
Proxy
封裝的目標對象。可以是任何類型的對象,包括原生數組,函數,甚至可以是另外一個代理對象。 -
handler:一個對象,其屬性是一批可選的函數,這些函數定義了對應的操作被執行時代理的行爲。
調用 Proxy.revocable
方法之後,其返回值是一個對象,其結構爲:{"proxy": proxy, "revoke": revoke}
,其中:
-
proxy:表示新生成的代理對象本身,和用一般方式
new Proxy(target, handler)
創建的代理對象沒什麼不同,只是它可以被撤銷掉。 -
revoke:撤銷方法,調用的時候不需要加任何參數,就可以撤銷掉和它一起生成的那個代理對象。
瞭解完 revocable
方法之後,我們來舉一個具體的示例:
1const target = {};
2const handler = {};
3const { proxy, revoke } = Proxy.revocable(target, handler);
4
5proxy.name = "阿寶哥";
6console.log(proxy.name); // 阿寶哥
7
8revoke();
9console.log(proxy.name); // TypeError: Revoked
10
11
當以上代碼成功運行之後,控制檯會輸出以下內容:
1阿寶哥
2Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
3 at <anonymous>
4
5
通過觀察以上的結果,我們可知當 proxy
對象被撤銷之後,我們就沒有辦法對已撤銷的 proxy
對象執行任何操作。
六、Proxy 在開源項目中的應用
因爲 Proxy 對象能夠提供強大的攔截能力,所以它被應用在一些成熟的開源項目中,用於實現響應式的功能,比如 vue-next 和 observer-util 項目。對於 observer-util 這個項目,阿寶哥已經寫了一篇 從觀察者模式到響應式的設計原理 的文章來介紹該項目,感興趣的小夥伴可以自行閱讀。
而對於 vue-next 項目來說,響應式的功能被封裝在 @vue/reactivity
模塊中,該模塊爲我們提供了一個 reactive
函數來創建響應式對象。下面我們來簡單瞭解一下 reactive
函數的實現:
1// packages/reactivity/src/reactive.ts
2export function reactive(target: object) {
3 if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
4 return target
5 }
6 return createReactiveObject(
7 target,
8 false,
9 mutableHandlers,
10 mutableCollectionHandlers
11 )
12}
13
14
在 reactive
函數內部,會繼續調用 createReactiveObject
函數來創建響應式對象,該函數也是被定義在 reactive.ts
文件中,該函數的的具體實現如下:
1// packages/reactivity/src/reactive.ts
2function createReactiveObject(
3 target: Target,
4 isReadonly: boolean,
5 baseHandlers: ProxyHandler<any>,
6 collectionHandlers: ProxyHandler<any>
7) {
8 // 省略部分代碼
9 const proxyMap = isReadonly ? readonlyMap : reactiveMap
10 const existingProxy = proxyMap.get(target)
11 if (existingProxy) {
12 return existingProxy
13 }
14 const proxy = new Proxy(
15 target,
16 targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
17 )
18 proxyMap.set(target, proxy)
19 return proxy
20}
21
22
在 createReactiveObject
函數內部,我們終於見到了期待已久的 Proxy
對象。當 target
對象不是集合類型的對象,比如 Map、Set、WeakMap 和 WeakSet 時,在創建 Proxy 對象時,使用的是 baseHandlers
,該 handler
對象定義了以下 5 種捕獲器:
1export const mutableHandlers: ProxyHandler<object> = {
2 get,
3 set,
4 deleteProperty,
5 has,
6 ownKeys
7}
8
9
其中 get
和 set
捕獲器是分別用於收集 effect 函數和觸發 effect 函數的執行。好了,這裏阿寶哥只是介紹一下 @vue/reactivity
中的 reactive
函數,關於該模塊是如何實現響應式的細節,這裏就不展開介紹了,阿寶哥後續會單獨寫一篇文章來詳細分析該模塊的功能。
七、參考資源
-
MDN - Proxy
-
MDN - Reflect
-
exploringjs - proxies
-
stackoverflow - what-is-a-receiver-in-javascript
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/UCgFeW9jHE2B_AhcRdGAtg