JavaScript 代理的驚人力量
今天我們要學習的是 ECMAScript 6 Proxies。我們將在本文中涉及以下主題。
-
什麼是代理
-
代理人在行動
-
誰使用代理
-
使用案例和實例
-
資源簡介
開始吧:)
什麼是代理
正如 MDN 網站上所說。
Proxy 對象可以讓你爲另一個對象創建一個代理,它可以攔截和重新定義該對象的基本操作。
在解釋什麼是 Proxy 的時候,說它可以創建一個 Proxy,這有點搞笑。當然,他們並沒有說錯,但是我們可以簡化這個說法,讓它更加友好。
Proxy 對象使你能夠包裝目標對象 通過這樣可以攔截和重新定義該對象的基本操作。
基本上,它的意思是說,我們要把一個對象,用 Proxy 包裹起來,這將允許我們創建一個 "隱藏" 的門,並控制所有對所需對象的訪問。
一個小插曲,Proxy 也是一種軟件設計模式,你一定要讀一讀(維基百科鏈接)。
一個 Proxy 的創建有兩個參數。
-
target: 你想包裹的原始對象 (proxy)
-
handler:定義哪些操作將被攔截,以及如何重新定義被攔截的操作的對象,也可以調用 "陷阱"。
代碼
大多數瀏覽器都支持代理功能,但也有一些老的瀏覽器不支持(當然是 IE),你可以在這裏查看完整的列表。google 有一個代理的 polyfill,但它不支持所有的代理功能。
現在知道了什麼是 Proxies,想看看能用它做什麼。
代理人在行動
讓我們想象一下,我們是一家銀行或一個憂心忡忡的女朋友。我們想知道每次銀行賬戶餘額被訪問和被通知的時間。我們將使用最簡單的處理程序操作 / trap: get
1const bankAccount = {
2 balance: 2020,
3 name: 'Georgy Glezer'
4};
5const handler = {
6 get: function(target, prop, receiver) {
7 if (prop === 'balance') {
8 console.log(`Current Balance Of: ${target.name} Is: ${target.balance} `);
9 }
10 return target[prop];
11 }
12};
13const wrappedBankAcount = new Proxy(bankAccount, handler);
14wrappedBankAcount.balance; // access to the balance
15// OUTPUT:
16// Current Balance Of: Georgy Glezer Is: 2020
17// 2020
18
在上面的例子中,我們有一個銀行賬戶對象,裏面有我的名字和 2020 的餘額。
這次的處理者對象是實現 get 操作 / trap,它接收一個有 3 個參數的函數和 get 的返回值。
-
target: 被訪問的對象(我們封裝的對象)。
-
prop:被訪問的對象(我們封裝的對象)。在我們的例子中被訪問的屬性,這裏是 "balance"。
-
receiver:接收者。可以是代理,也可以是繼承自代理的對象。
我們定義了一個條件,如果被訪問的屬性是 "餘額",我們將通知 (log) 餘額和當前用戶名,並返回 "餘額" 屬性。
從輸出中可以看到,一旦 "balance" 屬性被訪問,我們就通過使用 Proxy 和設置 get 操作 / 陷阱,很容易地通知 (log) 了這次訪問。
繼續我們銀行的想法,要求每次有人從銀行賬戶中取錢,我們都要得到通知。而另一個約束條件是,銀行不允許出現負餘額。爲了達到這個目的,我們這次要使用設置處理程序 / 陷阱。
1const bankAccount = {
2 balance: 2020,
3 name: 'Georgy Glezer'
4};
5const handler = {
6 set: function (obj, prop, value) {
7 console.log(`Current Balance: ${obj.balance}, New Balance: ${value}`);
8 if (value < 0) {
9 console.log(`We don't allow Negative Balance!`);
10 return false;
11 }
12 obj[prop] = value;
13 return true;
14 }
15};
16const wrappedBankAcount = new Proxy(bankAccount, handler);
17wrappedBankAcount.balance -= 2000; // access to the balance
18console.log(wrappedBankAcount.balance);
19wrappedBankAcount.balance -= 50; // access to the balance
20console.log(wrappedBankAcount.balance);
21// OUTPUT:
22// Current Balance: 2020, New Balance: 20
23// 20
24// Current Balance: 20, New Balance: -30
25// We don't allow Negative Balance!
26// 20
27
在上面的例子中,我們通知當前的餘額和取款後的新餘額,如果新的餘額是負數,我們也會通知並中止取款操作。
我們使用的是 set operator/trap,它是一個返回布爾值 (true/false) 的函數,用來判斷更新操作是否成功。它接收以下參數。
-
target: 被訪問的對象(我們封裝的對象)。
-
prop.prop:被訪問的對象(我們封裝的對象)。在我們的例子中,被訪問的屬性是 "balance"。
-
值。應該更新的新值。
-
receiver:接收器。賦值最初指向的對象。這通常是代理本身。但是 set() 處理程序也可以間接調用,通過原型鏈或其他各種方式。
你可以看到,它和 get 真的很相似,但只是多接收了 1 個新值的參數。
這 2 個操作符 / 陷阱是最常見的,如果你有興趣找到所有現有的操作符 / 陷阱,你可以在這裏查看。
誰使用代理
許多流行的庫都使用了這種技術,例如。
-
MobX
-
Vue
-
沉浸式
還有更多...... 他們中的大多數人都利用了 Proxies 給我們帶來的驚人力量,併爲我們提供了很棒的庫。
使用案例和實例
我們已經看到,我們可以使用代理服務器來進行。
-
登錄(通知銀行)
-
驗證 (阻止負值更新)
緩存
我們將再次使用 get operator/trap,並將 "dollars" 屬性添加到我們的對象中。在每次訪問 "dollars" 屬性時,我們將計算我們的餘額價值多少美元。因爲計算可能是一個沉重的操作,我們希望儘可能多的 Cache 它。
1const bankAccount = {
2 balance: 10,
3 name: 'Georgy Glezer',
4 get dollars() {
5 console.log('Calculating Dollars');
6 return this.balance *3.43008459;
7 }
8};
9let cache = {
10 currentBalance: null,
11 currentValue: null
12};
13const handler = {
14 get: function (obj, prop) {
15 if (prop === 'dollars') {
16 let value = cache.currentBalance !== obj.balance ? obj[prop] : cache.currentValue;
17 cache.currentValue = value;
18 cache.currentBalance = obj.balance;
19 return value;
20 }
21 return obj[prop];
22 }
23};
24const wrappedBankAcount = new Proxy(bankAccount, handler);
25console.log(wrappedBankAcount.dollars);
26console.log(wrappedBankAcount.dollars);
27console.log(wrappedBankAcount.dollars);
28console.log(wrappedBankAcount.dollars);
29// OUTPUT:
30// Calculating Dollars
31// 34.3008459
32// 34.3008459
33// 34.3008459
34// 34.3008459
35
正如你在例子中所看到的,我們有一個緩存對象,它保存着當前的銀行餘額和以美元爲單位的餘額價值。每次有人訪問 "dollar" 屬性時,我們都會先進行計算,然後將其緩存起來。
Dom 操作
我們想在每次餘額發生變化時更新屏幕上的文字。我們將使用一個 set 操作符 / trap,每次改變數值時,我們將更新屏幕上的 DOM 元素。
1const bankAccount = {
2 balance: 2020,
3 name: "Georgy Glezer",
4 get text() {
5 return `${this.name} Balance Is: ${this.balance}`;
6 }
7};
8const objectWithDom = (object, domId) => {
9 const handler = {
10 set: function (obj, prop, value) {
11 obj[prop] = value;
12 document.getElementById(domId).innerHTML = obj.text;
13 return true;
14 }
15 };
16 return new Proxy(object, handler);
17};
18// create a dom element with id: bank-account
19const wrappedBankAccount = objectWithDom(bankAccount, "bank-account");
20wrappedBankAccount.balance = 26;
21wrappedBankAccount.balance = 100000;
22
在這裏,我們創建了一個輔助函數,這樣我們就可以存儲 DOM 元素的 ID,並在 set operator/trap 中添加了簡單的行來更新 DOM 元素。很簡單,對吧?讓我們看看結果:)
概要
綜上所述,我們瞭解了 ECMAScript 6 Proxies,我們如何使用它們,以及用於什麼目的。在我看來,代理是一個神奇的工具,你可以用它來做各種各樣的選擇,你只需要考慮什麼是最適合你的:) 。
資源
-
MDN Proxy
-
“Proxies are awesome” Brendan Eich presentation at JSConf
-
Proxy Design Pattern
關於本文 譯者:飄飄 | 譯文來源:前端早讀課 作者:@Georgy Glezer 原文:https://levelup.gitconnected.com/the-amazing-power-of-javascript-proxies-aa27c6d06bcb
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/aaU31A6VVNgy3aFDbvOEdw