代理模式
1 定義
為其他對象提供一種代理以控制對這個(gè)對象的訪問
在某些情況下,一個(gè)對象不適合或者不能直接引用另一個(gè)對象井濒,而代理對象可以在客戶端和目標(biāo)對象之間起到中介的作用。
2 應(yīng)用舉例
2.1 緩存代理
現(xiàn)在我們有一個(gè)可以查詢城市經(jīng)緯度的函數(shù):
const getLatLng = (address) => {
if (address === "Beijing") {
return "北京經(jīng)緯度";
} else if (address === "Hangzhou") {
return "杭州經(jīng)緯度";
} else if (address === "Shanghai") {
return "上海經(jīng)緯度";
} else if (address === "Nanjing") {
return "南京經(jīng)緯度";
} else {
return "";
}
};
如果我們多次查詢南京的經(jīng)緯度,每次都要經(jīng)過 4 次判斷矩屁,我們通過 getLatLngProxy 函數(shù)將查詢結(jié)果緩存下來抡柿,從而避免多次重復(fù)判斷
const getLatLngProxy = ((fn) => {
const geoCache = {};
return (address) => {
console.log("緩存=" + geoCache[address]);
return (geoCache[address] ??= fn(address));
};
})(getLatLng);
getLatLngProxy("Nanjing"); // 緩存=undefined
getLatLngProxy("Nanjing"); // 緩存=南京經(jīng)緯度
4 次判斷看不出什么舔琅,但是如果 getLatLng 中的操作不是判斷,而是需要很復(fù)雜的計(jì)算洲劣,需要消耗很長時(shí)間备蚓,這時(shí)緩存的優(yōu)勢就很明顯了
我們在不修改原函數(shù)的前提下,通過高階函數(shù)創(chuàng)建了一個(gè)擁有緩存效果的代理函數(shù)
2.2 Vue2 響應(yīng)式原理——數(shù)據(jù)代理
如果你學(xué)習(xí)過 Vue2 響應(yīng)式原理囱稽,一定知道其中重要的一環(huán):數(shù)據(jù)代理郊尝。不知道也沒關(guān)系,下面舉個(gè)簡單的栗子來說明一下战惊。
const obj = {
name: "JiMing",
};
let name = obj.name; // 訪問 obj.name
obj.name = "Ji"; // 修改 obj.name
假設(shè)現(xiàn)在有一個(gè)對象 obj流昏,如果我想在訪問或修改obj.name
時(shí)做一些額外的操作,比如打印信息到控制臺(tái)吞获,該如何實(shí)現(xiàn)况凉?
JS 提供了 **Object.defineProperty()**
方法,該方法可以在一個(gè)對象上定義一個(gè)新屬性各拷,或者修改一個(gè)對象的現(xiàn)有屬性刁绒,并返回此對象。
我們可以利用這個(gè) API 在代理對象上添加目標(biāo)對象的同名屬性烤黍,同時(shí)添加額外的操作
const proxyObj = {}; // 代理對象
Object.defineProperty(proxyObj, "name", {
get() {
console.log("訪問了 obj.name");
return obj.name;
},
set(val) {
console.log("修改了 obj.name");
obj.name = val;
},
});
現(xiàn)在我們只要訪問或修改代理對象的 name 屬性知市,就可以實(shí)現(xiàn)訪問或修改obj.name
傻盟,同時(shí)打印信息到控制臺(tái)
Vue2 就是通過此方法將 data 中的屬性添加到 vm 實(shí)例上,因此我們可以使用this.屬性名
來訪問屬性初狰,并且和我們打印信息到控制臺(tái)一樣莫杈,Vue 也添加了額外的操作比如通過 set 實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽,從而完成響應(yīng)式變化
小結(jié)
- 根據(jù)單一職責(zé)原則:就一個(gè)類(通常也包括對象和函數(shù))而言奢入,應(yīng)該只有一個(gè)職責(zé)筝闹。
- 我們利用代理模式讓代理對象承擔(dān)額外功能,不破壞目標(biāo)對象腥光,從而不至于讓目標(biāo)對象變得臃腫而降低復(fù)用性和可維護(hù)性
3 JavaScript Proxy
JS 提供了 Proxy
類关顷,可以非常方便地創(chuàng)建代理對象,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找武福、賦值议双、枚舉、函數(shù)調(diào)用等)捉片。
Proxy 的用法非常簡單:
const proxy = new Proxy(target, handler)
// target
// 要使用 Proxy 包裝的目標(biāo)對象(可以是任何類型的對象平痰,包括原生數(shù)組,函數(shù)伍纫,甚至另一個(gè)代理)宗雇。
// handler
// 一個(gè)通常以函數(shù)作為屬性的對象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 p 的行為莹规。
詳見 MDN 文檔 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
3.1 Proxy 實(shí)現(xiàn)緩存代理
handler 對象有很多可選方法赔蒲,其中 apply 方法用來攔截函數(shù)調(diào)用操作
apply 方法接受 3 個(gè)參數(shù),詳見https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
// apply 的 3 個(gè)參數(shù)
// target 目標(biāo)對象
// thisArg 被調(diào)用時(shí)的上下文對象
// argArray 被調(diào)用時(shí)的參數(shù)數(shù)組良漱。
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"); // 緩存=杭州經(jīng)緯度
我們調(diào)用代理函數(shù) getLatLngProxy 時(shí)會(huì)觸發(fā) apply 方法
注意這里我們的目標(biāo)對象是 getLatLng 函數(shù)舞虱,即 apply 的 target 就是 getLatLng 的引用,因此我們調(diào)用 target 就相當(dāng)于調(diào)用 getLatLng
3.2 Vue3 的數(shù)據(jù)代理
Vue2 使用 Object.defineProperty 來實(shí)現(xiàn)數(shù)據(jù)代理母市,但是這個(gè)方法存在局限性矾兜,比如:普通屬性我們可以通過 set 方法獲取到其變化的信息,但是使用 push 方法改變數(shù)組患久,無法通過 set 獲取到焕刮。
因此 Vue3 改用 Proxy 來實(shí)現(xiàn)數(shù)據(jù)代理
和 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 目標(biāo)對象 即 obj
// property 被獲取的屬性名配并。
get(target, property) {
console.log(`訪問了 obj.${property}`);
return target[property];
},
// target 目標(biāo)對象 即 obj
// 將被設(shè)置的屬性名
set(target, property, value) {
console.log(`修改了 obj.${property}`);
target[property] = value;
},
});
proxyObj.name; // 訪問了 obj.name
proxyObj.name = "Ji"; // 修改了 obj.name
完結(jié),撒花??