代理模式
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
完結,撒花??
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/503405.html
標籤:JavaScript
上一篇:【微信小程式】知識點:頁面導航
