Proxy
åºçº¿
广æ³å¯ç¨
èª 2016å¹´9æ èµ·ï¼æ¤ç¹æ§å·²å¨ä¸»æµæµè§å¨ä¸å¾å°æ¯æï¼å¯å¨å¤§å¤æ°è®¾å¤åæµè§å¨çæ¬ä¸æ£å¸¸ä½¿ç¨ã
Proxy 对象å
è®¸ä½ ä¸ºå¦ä¸ä¸ªå¯¹è±¡å建代çï¼è¯¥ä»£çè½å¤æ¦æªå¹¶éæ°å®ä¹è¯¥å¯¹è±¡çåºæ¬æä½ã
æè¿°
Proxy 对象å
è®¸ä½ å建ä¸ä¸ªå¯æ¿ä»£åå§å¯¹è±¡ç对象ï¼ä½è¯¥å¯¹è±¡å¯è½éå®ä¹è·åã设置åå®ä¹å±æ§çåºç¡ Object æä½ã代ç对象常ç¨äºè®°å½å±æ§è®¿é®ãéªè¯ãæ ¼å¼åææ¸
çè¾å
¥çåºæ¯ã
å建 Proxy éæä¾ä¸¤ä¸ªåæ°ï¼
targetï¼éè¦ä»£ççåå§å¯¹è±¡handlerï¼å®ä¹åªäºæä½å°è¢«æ¦æªä»¥åå¦ä½éå®ä¹æ¦æªæä½ç对象
ä¾å¦ï¼æ¤æ®µä»£ç 为 target 对象å建äºä»£çï¼
const target = {
message1: "大家",
message2: "好",
};
const handler1 = {};
const proxy1 = new Proxy(target, handler1);
ç±äº handler æ¯ç©ºçï¼æ¤ä»£ççè¡ä¸ºå¦åç´æ¥å¯¹æºå¯¹è±¡è¿è¡æä½ï¼
console.log(proxy1.message1); // 大家
console.log(proxy1.message2); // 好
è¦èªå®ä¹ä»£çï¼æä»¬å¨ handler 对象ä¸å®ä¹å½æ°ï¼
const target = {
message1: "大家",
message2: "好",
};
const handler2 = {
get(target, prop, receiver) {
return "ä½ å¥½ä¸ç";
},
};
const proxy2 = new Proxy(target, handler2);
è¿éæä»¬æä¾äºä¸ä¸ª get() å¤çå¨çå®ç°ï¼å®ä¼æ¦æªå¯¹ç®æ å¯¹è±¡å±æ§è®¿é®çå°è¯ã
å¤çå¨å½æ°ææ¶è¢«ç§°ä¸ºé·é±ï¼å¤§æ¦æ¯å 为å®ä»¬ä¼æè·å¯¹ç®æ 对象çè°ç¨ã䏿 handler2 ä¸çé·é±éæ°å®ä¹äºææå±æ§è®¿é®å¨ï¼
console.log(proxy2.message1); // ä½ å¥½ä¸ç
console.log(proxy2.message2); // ä½ å¥½ä¸ç
代çå¸¸ä¸ Reflect 对象é
å使ç¨ï¼è¯¥å¯¹è±¡æä¾äºä¸äºä¸ Proxy é·é±ååçæ¹æ³ãReflect æ¹æ³éè¿è°ç¨å¯¹åºç对象å
鍿¹æ³æ¥å®ç°åå°è¯ä¹ãä¾å¦ï¼è¥ä¸å¸æéå®ä¹å¯¹è±¡è¡ä¸ºï¼å¯è°ç¨ Reflect.getï¼
const target = {
message1: "大家",
message2: "好",
};
const handler3 = {
get(target, prop, receiver) {
if (prop === "message2") {
return "ä½ å¥½ä¸ç";
}
return Reflect.get(...arguments);
},
};
const proxy3 = new Proxy(target, handler3);
console.log(proxy3.message1); // 大家
console.log(proxy3.message2); // ä½ å¥½ä¸ç
Reflect æ¹æ³ä»éè¿å¯¹è±¡å
鍿¹æ³ä¸å¯¹è±¡äº¤äºââè¥å¨ä»£çä¸è°ç¨è¯¥æ¹æ³ï¼å®ä¸ä¼âè§£é¤ä»£çåâãè¥å¨ä»£çé·é±ä¸ä½¿ç¨ Reflect æ¹æ³ï¼ä¸è¯¥æ¹æ³è°ç¨å次被é·é±æ¦æªï¼åå¯è½å¼åæ ééå½ã
æ¯è¯
以䏿¯è¯ç¨äºæè¿°ä»£ççåè½ç¹æ§ã
- handler
-
ä½ä¸º
Proxyæé 彿°ç第äºä¸ªåæ°ä¼ éç对象ãå®å å«å®ä¹ä»£çè¡ä¸ºçé·é±ã - é·é±ï¼trapï¼
-
å®ä¹å¯¹åºå¯¹è±¡å 鍿¹æ³è¡ä¸ºç彿°ï¼ç±»ä¼¼äºæä½ç³»ç»ä¸çé·é±æ¦å¿µãï¼
- ç®æ ï¼targetï¼
-
代çèæåç对象ãå®é常ä½ä¸ºä»£ççåå¨å端使ç¨ãå ³äºå¯¹è±¡ä¸å¯æ©å±æ§æä¸å¯é ç½®å±æ§çä¸åæ§ï¼ä¿æä¸åçè¯ä¹ï¼å°éå¯¹ç®æ 对象è¿è¡éªè¯ã
- ä¸åé
-
å¨å®ç°èªå®ä¹æä½æ¶ä¿æä¸åçè¯ä¹ã妿é·é±å®ç°è¿åäºå¤çå¨çä¸åæ§ï¼å°æåº
TypeErrorå¼å¸¸ã
对象å 鍿¹æ³
对象æ¯å±æ§çéåãç¶èï¼è¯¥è¯è¨å¹¶æªæä¾ä»»ä½æºå¶æ¥ç´æ¥æä½å¯¹è±¡ä¸åå¨çæ°æ®ââç¸åï¼å¯¹è±¡å®ä¹äºä¸äºå
鍿¹æ³æ¥è§å®å
¶äº¤äºæ¹å¼ãä¾å¦ï¼å½ä½ 读å obj.x æ¶ï¼ä½ å¯è½ä¼ææåç以䏿
åµï¼
x屿§ä¼æ²¿çååé¾ä¸è¡æç´¢ï¼ç´è³æ¾å°è¯¥å±æ§ã- è¥
xæ¯æ°æ®å±æ§ï¼åè¿å屿§æè¿°ç¬¦çvalue屿§ã - è¥
xæ¯è®¿é®å¨å±æ§ï¼åè°ç¨è·åå¨ï¼å¹¶è¿åè·åå¨çè¿åå¼ã
è¿ç§è¿ç¨å¨è¯è¨ä¸å¹¶æ ç¹æ®ä¹å¤ââä»
ä»
æ¯å 为æ®é对象é»è®¤å
·æä¸ä¸ªå为 [[Get]] çå
鍿¹æ³ï¼è¯¥æ¹æ³å³ä»¥è¿ç§è¡ä¸ºæ¹å¼å®ä¹ãobj.x 屿§è®¿é®è¯æ³åªæ¯è°ç¨äºå¯¹è±¡ç [[Get]] æ¹æ³ï¼è对象ä¼éè¿èªèº«å
鍿¹æ³çå®ç°æ¥å³å®è¿åä»ä¹å
容ã
å¦ä¸ä¸ªä¾åæ¯ï¼æ°ç»ä¸æ®é对象ä¸åï¼å 为å®ä»¬å
·æä¸ä¸ªç¥å¥ç length 屿§ââå½ä¿®æ¹è¯¥å±æ§æ¶ï¼ç³»ç»ä¼èªå¨ä¸ºæ°ç»åé
ç©ºæ§½ä½æç§»é¤å
ç´ ãåæ ·å°ï¼åæ°ç»æ·»å å
ç´ ä¼èªå¨æ¹å length 屿§ãè¿æ¯å 为æ°ç»æ¥æ [[DefineOwnProperty]] å
鍿¹æ³ï¼è¯¥æ¹æ³å¨åå
¥æ´æ°ç´¢å¼æ¶ä¼æ´æ° lengthï¼å¨åå
¥ length 弿¶åæ´æ°æ°ç»å
容ãè¿ç±»å
鍿¹æ³å®ç°ä¸æ®é对象ä¸åçç¹æ®å¯¹è±¡è¢«ç§°ä¸ºç¹æ®å¯¹è±¡ãProxy 使å¼åè
è½å¤å
¨æå®ä¹èªå®ä¹çç¹æ®å¯¹è±¡ã
ææå¯¹è±¡åå ·æä»¥ä¸å 鍿¹æ³ï¼
| å 鍿¹æ³ | 对åºçé·é± |
|---|---|
[[GetPrototypeOf]] |
getPrototypeOf() |
[[SetPrototypeOf]] |
setPrototypeOf() |
[[IsExtensible]] |
isExtensible() |
[[PreventExtensions]] |
preventExtensions() |
[[GetOwnProperty]] |
getOwnPropertyDescriptor() |
[[DefineOwnProperty]] |
defineProperty() |
[[HasProperty]] |
has() |
[[Get]] |
get() |
[[Set]] |
set() |
[[Delete]] |
deleteProperty() |
[[OwnPropertyKeys]] |
ownKeys() |
彿°å¯¹è±¡è¿å ·æä»¥ä¸å 鍿¹æ³ï¼
| å 鍿¹æ³ | 对åºçé·é± |
|---|---|
[[Call]] |
apply() |
[[Construct]] |
construct() |
éè¦è®¤è¯å°ï¼ä¸å¯¹è±¡çææäº¤äºæç»é½å½ç»ä¸ºè°ç¨è¿äºå
鍿¹æ³ä¹ä¸ï¼ä¸æææ¹æ³åå¯éè¿ä»£çè¿è¡å®å¶ãè¿æå³çè¯è¨æ¬èº«å ä¹ä¸ä¿è¯ä»»ä½è¡ä¸ºï¼é¤æäºå
³é®ä¸åéå¤ï¼ââä¸åçç±å¯¹è±¡èªèº«å®ä¹ã彿§è¡ delete obj.x æ¶ï¼æ æ³ä¿è¯åç»æ§è¡ "x" in obj ä¼è¿å falseââè¿åå³äºå¯¹è±¡å¯¹ [[Delete]] å [[HasProperty]] æ¹æ³çå
·ä½å®ç°ãdelete obj.x æä½å¯è½åæ§å¶å°è¾åºæ¥å¿ãä¿®æ¹å
¨å±ç¶æï¼çè³å¯è½å®ä¹æ°å±æ§èéå é¤åæå±æ§ï¼å°½ç®¡å¨ç¼åä»£ç æ¶åºé¿å
æ¤ç±»è¯ä¹è¡ä¸ºã
ææå
鍿¹æ³åç±è¯è¨æ¬èº«è°ç¨ï¼æ æ³å¨ JavaScript 代ç ä¸ç´æ¥è®¿é®ãReflect å½åç©ºé´æä¾çæ¹æ³é¤æ§è¡è¾å
¥è§èå/éªè¯å¤ï¼ä¸»è¦åè½å°±æ¯è°ç¨è¿äºå
鍿¹æ³ã卿¯ä¸ªé·é±ç页é¢ä¸ï¼æä»¬ååºäºè§¦å该é·é±çå
¸ååºæ¯ï¼ä½è¿äºå
鍿¹æ³å¨å¤§éåºæ¯ä¸è¢«è°ç¨ãä¾å¦æ°ç»æ¹æ³éè¿è¿äºå
鍿¹æ³è¯»åæ°ç»ï¼å æ¤è¯¸å¦ push() ä¹ç±»çæ¹æ³ä¹ä¼è§¦å get() å set() é·é±ã
大夿°å
鍿¹æ³çåè½é½å¾ç´è§ãå¯ä¸å¯è½ä»¤äººæ··æ·çæ¯ [[Set]] å [[DefineOwnProperty]]ãå¯¹äºæ®é对象ï¼åè
ä¼è°ç¨ setterï¼åè
åä¸ä¼ï¼ä¸å½ä¸åå¨å±æ§æå±æ§ä¸ºæ°æ®å±æ§æ¶ï¼[[Set]] ä¼å
é¨è°ç¨[[DefineOwnProperty]]ãï¼è½ç¶ä½ å¯è½ç¥é obj.x = 1 è¯æ³ä½¿ç¨ [[Set]]ï¼è Object.defineProperty() ä½¿ç¨ [[DefineOwnProperty]]ï¼ä½å
¶ä»å
ç½®æ¹æ³åè¯æ³éç¨ä½ç§è¯ä¹å¹¶ä¸ç´è§ãä¾å¦ï¼ç±»å段 ä½¿ç¨ [[DefineOwnProperty]] è¯ä¹ï¼å æ¤å½æ´¾ç类声æå段æ¶ï¼ç¶ç±»ä¸å®ä¹ç setter ä¸ä¼è¢«è°ç¨ã
æé 彿°
Proxy()-
å建ä¸ä¸ªæ°ç
Proxy对象ã
夿³¨ï¼ä¸åå¨ Proxy.prototype 屿§ï¼æ
Proxy çå®ä¾æ²¡æç¹æ®ç屿§ææ¹æ³ã
éææ¹æ³
Proxy.revocable()-
å建ä¸ä¸ªå¯æ¤éç
Proxy对象ã
示ä¾
>åºæ¬ç¤ºä¾
å¨ä»¥ä¸ç®åçä¾åä¸ï¼å½å¯¹è±¡ä¸ä¸åå¨å±æ§åæ¶ï¼é»è®¤è¿åå¼ä¸º 37ãä¸é¢ç代ç 以æ¤å±ç¤ºäº get() å¤çå¨ç使ç¨åºæ¯ã
const handler = {
get(obj, prop) {
return prop in obj ? obj[prop] : 37;
},
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log("c" in p, p.c); // false, 37
æ æä½è½¬å代ç
å¨ä»¥ä¸ä¾åä¸ï¼æä»¬ä½¿ç¨äºä¸ä¸ªåç JavaScript 对象ï¼ä»£çä¼å°ææåºç¨å°å®çæä½è½¬åå°è¿ä¸ªå¯¹è±¡ä¸ã
let target = {};
let p = new Proxy(target, {});
p.a = 37; // æä½è½¬åå°ç®æ
console.log(target.a); // 37. æä½å·²ç»è¢«æ£ç¡®å°è½¬å
请注æï¼è½ç¶è¿ç§âæ æä½â对æ®é JavaScript 对象ææï¼ä½å¯¹åç对象ï¼å¦ DOM å
ç´ ãMap 对象æä»»ä½å
·æå
鍿§½çå¯¹è±¡ï¼æ æãæ´å¤ä¿¡æ¯è¯·åé
ä¸è½¬åç§æåæ®µã
ä¸è½¬åç§æåæ®µ
代ç仿¯å ·æä¸å身份çå¦ä¸ä¸ªå¯¹è±¡ââ宿¯å¨è¢«å°è£ 对象ä¸å¤é¨ä¹é´è¿ä½ç代çãå æ¤ï¼ä»£çæ æ³ç´æ¥è®¿é®åå§å¯¹è±¡çç§æå ç´ ã
class Secret {
#secret;
constructor(secret) {
this.#secret = secret;
}
get secret() {
return this.#secret.replace(/\d+/, "[å·²å é¤]");
}
}
const secret = new Secret("123456");
console.log(secret.secret); // [å·²å é¤]
// çèµ·æ¥åæ¯æ æä½è½¬å...
const proxy = new Proxy(secret, {});
console.log(proxy.secret); // TypeError: Cannot read private member #secret from an object whose class did not declare it
è¿æ¯å 为å½ä»£çç get é·é±è¢«è°ç¨æ¶ï¼this 弿¯ proxy èéåå§ç secretï¼å æ¤æ æ³è®¿é® #secretãè¦è§£å³æ¤é®é¢ï¼è¯·å°åå§ç secret ä½ä¸º this 使ç¨ï¼
const proxy = new Proxy(secret, {
get(target, prop, receiver) {
// é»è®¤æ
åµä¸ï¼å®çèµ·æ¥å Reflect.get(target, prop, receiver)ï¼
// å
¶ä¸ `this` çå¼ä¸å
return target[prop];
},
});
console.log(proxy.secret);
å¯¹äºæ¹æ³èè¨ï¼è¿æå³çä½ è¿éè¦å°æ¹æ³ç this å¼éå®åååå§å¯¹è±¡ï¼
class Secret {
#x = 1;
x() {
return this.#x;
}
}
const secret = new Secret();
const proxy = new Proxy(secret, {
get(target, prop, receiver) {
const value = target[prop];
if (value instanceof Function) {
return function (...args) {
return value.apply(this === receiver ? target : this, args);
};
}
return value;
},
});
console.log(proxy.x());
æäºåç JavaScript 对象å
·æå为å
鍿§½ç屿§ï¼è¿äºå±æ§æ æ³ä» JavaScript 代ç 访é®ãä¾å¦ï¼Map å¯¹è±¡æ¥æå为 [[MapData]] çå
鍿§½ï¼ç¨äºå卿 å°çé®å¼å¯¹ãå æ¤æ æ³ç®åå°ä¸ºæ å°å建转å代çï¼
const proxy = new Proxy(new Map(), {});
console.log(proxy.size); // TypeError: get size method called on incompatible Proxy
ä½ å¿
须使ç¨ä¸ææè¿°çâthis æ¢å¤âä»£çæ¥è§£å³è¿ä¸ªé®é¢ã
éªè¯
éè¿ Proxyï¼ä½ å¯ä»¥è½»æ¾å°éªè¯åä¸ä¸ªå¯¹è±¡çä¼ å¼ãä¸é¢ç代ç 忤å±ç¤ºäº set() å¤çå¨çä½ç¨ã
const validator = {
set(obj, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("å¹´é¾ä¸æ¯æ´æ°");
}
if (value > 200) {
throw new RangeError("å¹´é¾ä¸åæ³");
}
}
// é»è®¤è¡ä¸ºæ¯åå¨è¯¥å¼
obj[prop] = value;
// 表示éªè¯éè¿
return true;
},
};
const person = new Proxy({}, validator);
person.age = 100;
console.log(person.age); // 100
person.age = "young"; // æåºå¼å¸¸
person.age = 300; // æåºå¼å¸¸
æä½ DOM èç¹
卿¤ç¤ºä¾ä¸ï¼æä»¬ä½¿ç¨ Proxy æ¥åæ¢ä¸¤ä¸ªä¸åå
ç´ ç屿§ï¼å½ä¸ºä¸ä¸ªå
ç´ è®¾ç½®è¯¥å±æ§æ¶ï¼å¦ä¸ä¸ªå
ç´ ç屿§ä¼è¢«åæ¶è®¾ç½®ã
æä»¬å建ä¸ä¸ªå为 view ç对象ï¼è¯¥å¯¹è±¡ä½ä¸ºå
·æ selected 屿§ç对象ç代çã代çå¤çå¨å®ä¹äº set() å¤çå¨ã
å½æä»¬å° HTML å
ç´ èµå¼ç» view.selected æ¶ï¼è¯¥å
ç´ ç 'aria-selected' 屿§ä¼è¢«è®¾ç½®ä¸º trueãè¥éåå°å¦ä¸ä¸ªå
ç´ èµå¼ç» view.selectedï¼å该å
ç´ ç 'aria-selected' 屿§ä¼è¢«è®¾ç½®ä¸º trueï¼èå
åå
ç´ ç 'aria-selected' 屿§ä¼èªå¨è®¾ç½®ä¸º falseã
const view = new Proxy(
{
selected: null,
},
{
set: function (obj, prop, newval) {
let oldval = obj[prop];
if (prop === "selected") {
if (oldval) {
oldval.setAttribute("aria-selected", "false");
}
if (newval) {
newval.setAttribute("aria-selected", "true");
}
}
// é»è®¤è¡ä¸ºæ¯åå¨è¯¥å¼
obj[prop] = newval;
// 表示æä½æå
return true;
},
},
);
const item1 = document.getElementById("item-1");
const item2 = document.getElementById("item-2");
// éæ© item1:
view.selected = item1;
console.log(`item1: ${item1.getAttribute("aria-selected")}`);
// item1: true
// éæ© item2 å°åæ¶éæ© item1:
view.selected = item2;
console.log(`item1: ${item1.getAttribute("aria-selected")}`);
// item1: false
console.log(`item2: ${item2.getAttribute("aria-selected")}`);
// item2: true
å¼ä¿®æ£åéå 屿§
ä»¥ä¸ products 代çä¼è®¡ç®ä¼ å¼å¹¶æ ¹æ®éè¦è½¬æ¢ä¸ºæ°ç»ãè¿ä¸ªä»£çå¯¹è±¡åæ¶æ¯æä¸ä¸ªå«å latestBrowser çéå 屿§ï¼è¿ä¸ªå±æ§å¯ä»¥åæ¶ä½ä¸º getter å setterã
const products = new Proxy(
{
browsers: ["Firefox", "Chrome"],
},
{
get: function (obj, prop) {
// éå ä¸ä¸ªå±æ§
if (prop === "latestBrowser") {
return obj.browsers[obj.browsers.length - 1];
}
// é»è®¤è¡ä¸ºæ¯è¿å屿§å¼
return obj[prop];
},
set: function (obj, prop, value) {
// éå 屿§
if (prop === "latestBrowser") {
obj.browsers.push(value);
return;
}
// 妿䏿¯æ°ç»ï¼åè¿è¡è½¬æ¢
if (typeof value === "string") {
value = [value];
}
// é»è®¤è¡ä¸ºæ¯ä¿å屿§å¼
obj[prop] = value;
// 表示æä½æå
return true;
},
},
);
console.log(products.browsers);
// ['Firefox', 'Chrome']
products.browsers = "Safari";
// 妿ä¸å°å¿ä¼ å
¥äºä¸ä¸ªå符串
console.log(products.browsers);
// ['Safari'] <- 乿²¡é®é¢ï¼å¾å°ç便§æ¯ä¸ä¸ªæ°ç»
products.latestBrowser = "Edge";
console.log(products.browsers);
// ['Safari', 'Edge']
console.log(products.latestBrowser);
// 'Edge'
è§è
| è§è |
|---|
| ECMAScript® 2027 Language Specification> # sec-proxy-objects> |
æµè§å¨å ¼å®¹æ§
åè§
- Proxy 太æ£äºââBrendan Eich å¨ JSConf 大ä¼ï¼2014 å¹´ï¼çæ¼è®²