代理模式 1 定義 為其他對象提供一種代理以控制對這個對象的訪問 在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。 2 應用舉例 2.1 緩存代理 現在我們有一個可以查詢城市經緯度的函數: const getLatLng = (addres ...
代理模式
1 定義
為其他對象提供一種代理以控制對這個對象的訪問
在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
2 應用舉例
2.1 緩存代理
現在我們有一個可以查詢城市經緯度的函數:
const getLatLng = (address) => {
if (address === "Beijing") {
return "北京經緯度";
} else if (address === "Hangzhou") {
return "杭州經緯度";
} else if (address === "Shanghai") {
return "上海經緯度";
} else if (address === "Nanjing") {
return "南京經緯度";
} else {
return "";
}
};
如果我們多次查詢南京的經緯度,每次都要經過 4 次判斷,我們通過 getLatLngProxy 函數將查詢結果緩存下來,從而避免多次重覆判斷
const getLatLngProxy = ((fn) => {
const geoCache = {};
return (address) => {
console.log("緩存=" + geoCache[address]);
return (geoCache[address] ??= fn(address));
};
})(getLatLng);
getLatLngProxy("Nanjing"); // 緩存=undefined
getLatLngProxy("Nanjing"); // 緩存=南京經緯度
4 次判斷看不出什麼,但是如果 getLatLng 中的操作不是判斷,而是需要很複雜的計算,需要消耗很長時間,這時緩存的優勢就很明顯了
我們在不修改原函數的前提下,通過高階函數創建了一個擁有緩存效果的代理函數
2.2 Vue2 響應式原理——數據代理
如果你學習過 Vue2 響應式原理,一定知道其中重要的一環:數據代理。不知道也沒關係,下麵舉個簡單的慄子來說明一下。
const obj = {
name: "JiMing",
};
let name = obj.name; // 訪問 obj.name
obj.name = "Ji"; // 修改 obj.name
假設現在有一個對象 obj,如果我想在訪問或修改obj.name
時做一些額外的操作,比如列印信息到控制台,該如何實現?
JS 提供了 **Object.defineProperty()**
方法,該方法可以在一個對象上定義一個新屬性,或者修改一個對象的現有屬性,並返回此對象。
我們可以利用這個 API 在代理對象上添加目標對象的同名屬性,同時添加額外的操作
const proxyObj = {}; // 代理對象
Object.defineProperty(proxyObj, "name", {
get() {
console.log("訪問了 obj.name");
return obj.name;
},
set(val) {
console.log("修改了 obj.name");
obj.name = val;
},
});
現在我們只要訪問或修改代理對象的 name 屬性,就可以實現訪問或修改obj.name
,同時列印信息到控制台
Vue2 就是通過此方法將 data 中的屬性添加到 vm 實例上,因此我們可以使用this.屬性名
來訪問屬性,並且和我們列印信息到控制台一樣,Vue 也添加了額外的操作比如通過 set 實現數據監聽,從而完成響應式變化
小結
- 根據單一職責原則:就一個類(通常也包括對象和函數)而言,應該只有一個職責。
- 我們利用代理模式讓代理對象承擔額外功能,不破壞目標對象,從而不至於讓目標對象變得臃腫而降低復用性和可維護性
3 JavaScript Proxy
JS 提供了 Proxy
類,可以非常方便地創建代理對象,從而實現基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數調用等)。
Proxy 的用法非常簡單:
const proxy = new Proxy(target, handler)
// target
// 要使用 Proxy 包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)。
// handler
// 一個通常以函數作為屬性的對象,各屬性中的函數分別定義了在執行各種操作時代理 p 的行為。
詳見 MDN 文檔 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
3.1 Proxy 實現緩存代理
handler 對象有很多可選方法,其中 apply 方法用來攔截函數調用操作
apply 方法接受 3 個參數,詳見https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
// apply 的 3 個參數
// target 目標對象
// thisArg 被調用時的上下文對象
// argArray 被調用時的參數數組。
const geoCache = {};
const getLatLngProxy = new Proxy(getLatLng, {
apply(target, thisArg, argArray) {
const address = argArray[0];
console.log("緩存=" + geoCache[address]);
return (geoCache[address] ??= target(address));
},
});
getLatLngProxy("Hangzhou"); // 緩存=undefined
getLatLngProxy("Hangzhou"); // 緩存=杭州經緯度
我們調用代理函數 getLatLngProxy 時會觸發 apply 方法
註意這裡我們的目標對象是 getLatLng 函數,即 apply 的 target 就是 getLatLng 的引用,因此我們調用 target 就相當於調用 getLatLng
3.2 Vue3 的數據代理
Vue2 使用 Object.defineProperty 來實現數據代理,但是這個方法存在局限性,比如:普通屬性我們可以通過 set 方法獲取到其變化的信息,但是使用 push 方法改變數組,無法通過 set 獲取到。
因此 Vue3 改用 Proxy 來實現數據代理
和 apply 方法類似,handler 中還有 get 和 set 方法用來攔截對屬性訪問、修改的操作
詳見
- get https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
- set https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
const obj = {
name: "JiMing",
};
const proxyObj = new Proxy(obj, {
// target 目標對象 即 obj
// property 被獲取的屬性名。
get(target, property) {
console.log(`訪問了 obj.${property}`);
return target[property];
},
// target 目標對象 即 obj
// 將被設置的屬性名
set(target, property, value) {
console.log(`修改了 obj.${property}`);
target[property] = value;
},
});
proxyObj.name; // 訪問了 obj.name
proxyObj.name = "Ji"; // 修改了 obj.name
完結,撒花