為其他物件提供一種代理以控制對這個物件的存取
在某些情況下,一個物件不適合或者不能直接參照另一個物件,而代理物件可以在使用者端和目標物件之間起到中介的作用。
現在我們有一個可以查詢城市經緯度的函數:
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 中的操作不是判斷,而是需要很複雜的計算,需要消耗很長時間,這時快取的優勢就很明顯了
我們在不修改原函數的前提下,通過高階函數建立了一個擁有快取效果的代理函數
如果你學習過 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 實現資料監聽,從而完成響應式變化
小結
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
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
Vue2 使用 Object.defineProperty 來實現資料代理,但是這個方法存在侷限性,比如:普通屬性我們可以通過 set 方法獲取到其變化的資訊,但是使用 push 方法改變陣列,無法通過 set 獲取到。
因此 Vue3 改用 Proxy 來實現資料代理
和 apply 方法類似,handler 中還有 get 和 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
完結,撒花