Proxyë í¹ì ê°ì²´ë¥¼ ê°ì¸ íë¡í¼í° ì½ê¸°, ì°ê¸°ì ê°ì ê°ì²´ì ê°í´ì§ë ìì
ì ì¤ê°ìì ê°ë¡ì±ë ê°ì²´ë¡, ê°ë¡ì±ì§ ìì
ì Proxy ìì²´ìì ì²ë¦¬ë기ë íê³ , ìë ê°ì²´ê° ì²ë¦¬íëë¡ ê·¸ëë¡ ì ë¬ë기ë í©ëë¤.
íë½ìë ë¤ìí ë¼ì´ë¸ë¬ë¦¬ì ëªëª ë¸ë¼ì°ì íë ììí¬ìì ì¬ì©ëê³ ììµëë¤. ì´ë² ì±í°ìì íë½ì를 ì´ë»ê² ì¤ë¬´ì ì ì©í ì ììì§ ë¤ìí ìì 를 íµí´ ì´í´ë³´ê² ìµëë¤.
Proxy
문ë²:
let proxy = new Proxy(target, handler)
targetâ ê°ì¸ê² ë ê°ì²´ë¡, í¨ì를 í¬í¨í 모ë ê°ì²´ê° ê°ë¥í©ëë¤.handlerâ ëìì ê°ë¡ì±ë ë©ìëì¸ 'í¸ë©(trap)'ì´ ë´ê¸´ ê°ì²´ë¡, ì¬ê¸°ì íë½ì를 ì¤ì í©ëë¤(ìì:getí¸ë©ìtargetì íë¡í¼í°ë¥¼ ì½ì ë,setí¸ë©ìtargetì íë¡í¼í°ë¥¼ ì¸ ë íì±íë¨).
proxyì ìì
ì´ ê°í´ì§ê³ , handlerì ìì
ê³¼ ììíë í¸ë©ì´ ìì¼ë©´ í¸ë©ì´ ì¤íëì´ íë½ìê° ì´ ìì
ì ì²ë¦¬í 기í를 ì»ê² ë©ëë¤. í¸ë©ì´ ìì¼ë©´ targetì ìì
ì´ ì§ì ìíë©ëë¤.
먼ì , í¸ë©ì´ ìë íë½ì를 ì¬ì©í ìì를 ì´í´ë´ ìë¤.
let target = {};
let proxy = new Proxy(target, {}); // ë¹ í¸ë¤ë¬
proxy.test = 5; // íë½ìì ê°ì ìëë¤. -- (1)
alert(target.test); // 5, targetì ìë¡ì´ íë¡í¼í°ê° ìê²¼ë¤ì!
alert(proxy.test); // 5, íë½ì를 ì¬ì©í´ ê°ì ì½ì ìë ììµëë¤. -- (2)
for(let key in proxy) alert(key); // test, ë°ë³µë ì ëìí©ëë¤. -- (3)
ì ììì íë½ìì í¸ë©ì´ ì기 ë문ì proxyì ê°í´ì§ë 모ë ìì
ì targetì ì ë¬ë©ëë¤.
proxy.test=를 ì´ì©í´ ê°ì ì°ë©´targetì ìë¡ì´ ê°ì´ ì¤ì ë©ëë¤.proxy.test를 ì´ì©í´ ê°ì ì½ì¼ë©´targetìì ê°ì ì½ì´ìµëë¤.proxy를 ëìì¼ë¡ ë°ë³µ ìì ì íë©´targetì ì ì¥ë ê°ì´ ë°íë©ëë¤.
그림ìì ë³¼ ì ìë¯ì´ í¸ë©ì´ ìì¼ë©´ proxyë targetì ëë¬ì¸ë í¬ëª
í ëí¼ê° ë©ëë¤.
Proxyë ì¼ë° ê°ì²´ìë ë¤ë¥¸ íë ììì ë³´ì´ë 'í¹ì ê°ì²´(exotic object)'ì
ëë¤. íë¡í¼í°ê° ìì£ . handlerê° ë¹ì´ìì¼ë©´ Proxyì ê°í´ì§ë ìì
ì targetì ê³§ë°ë¡ ì ë¬ë©ëë¤.
ì ì´ì í¸ë©ì ì¶ê°í´ Proxyì 기ë¥ì íì±íí´ë´
ìë¤.
ê·¸ ì ì 먼ì , í¸ë©ì ì¬ì©í´ ê°ë¡ì± ì ìë ìì ì 무ìì´ ìëì§ ììë´ ìë¤.
ê°ì²´ì ì´ë¤ ìì
ì í ë ìë°ì¤í¬ë¦½í¸ ëª
ì¸ìì ì ìë 'ë´ë¶ ë©ìë(internal method)'ê° ê¹ìí ê³³ìì ê´ì¬í©ëë¤. íë¡í¼í°ë¥¼ ì½ì ë [[Get]]ì´ë¼ë ë´ë¶ ë©ìëê°, íë¡í¼í°ì ì¸ ë [[Set]]ì´ë¼ë ë´ë¶ ë©ìëê° ê´ì¬íê² ëì£ . ì´ë° ë´ë¶ ë©ìëë¤ì ëª
ì¸ììë§ ì ìë ë©ìëì´ê¸° ë문ì ê°ë°ìê° ì½ë를 ì¬ì©í´ í¸ì¶í ì ììµëë¤.
íë½ìì í¸ë©ì ë´ë¶ ë©ìëì í¸ì¶ì ê°ë¡ì±ëë¤. íë½ìê° ê°ë¡ì±ë ë´ë¶ ë©ìë 리ì¤í¸ë ëª ì¸ììì íì¸í ì ìëë°, ìë íìë ì´ë¥¼ ì ë¦¬í´ ëììµëë¤.
모ë ë´ë¶ ë©ìëì ëìíë í¸ë©ì´ ììµëë¤. new Proxyì handlerì 매ê°ë³ìë¡ ì¶ê°í ì ìë ë©ìë ì´ë¦ì ìë íì âí¸ë¤ë¬ ë©ìëâ ì´ìì íì¸íì¤ ì ììµëë¤.
| ë´ë¶ ë©ìë | í¸ë¤ë¬ ë©ìë | ìë ìì |
|---|---|---|
[[Get]] |
get |
íë¡í¼í°ë¥¼ ì½ì ë |
[[Set]] |
set |
íë¡í¼í°ì ì¸ ë |
[[HasProperty]] |
has |
in ì°ì°ìê° ëìí ë |
[[Delete]] |
deleteProperty |
delete ì°ì°ìê° ëìí ë |
[[Call]] |
apply |
í¨ì를 í¸ì¶í ë |
[[Construct]] |
construct |
new ì°ì°ìê° ëìí ë |
[[GetPrototypeOf]] |
getPrototypeOf |
Object.getPrototypeOf |
[[SetPrototypeOf]] |
setPrototypeOf |
Object.setPrototypeOf |
[[IsExtensible]] |
isExtensible |
Object.isExtensible |
[[PreventExtensions]] |
preventExtensions |
Object.preventExtensions |
[[DefineOwnProperty]] |
defineProperty |
Object.defineProperty, Object.defineProperties |
[[GetOwnProperty]] |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor, for..in, Object.keys/values/entries |
[[OwnPropertyKeys]] |
ownKeys |
Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object/keys/values/entries |
ë´ë¶ ë©ìëë í¸ë©ì ì¸ ë ìë°ì¤í¬ë¦½í¸ìì ì í ëª ê°ì§ ê·ì¹(invariant)ì ë°ëì ë°ë¼ì¼ í©ëë¤.
ëë¶ë¶ì ê·ì¹ì ë°í ê°ê³¼ ê´ë ¨ëì´ììµëë¤.
- ê°ì ì°ë ê² ì±ê³µì ì¼ë¡ ì²ë¦¬ëìì¼ë©´
[[Set]]ì ë°ëìtrue를 ë°íí´ì¼ í©ëë¤. ê·¸ë ì§ ìì ê²½ì°ëfalse를 ë°íí´ì¼ í©ëë¤. - ê°ì ì§ì°ë ê² ì±ê³µì ì¼ë¡ ì²ë¦¬ëìì¼ë©´
[[Delete]]ë ë°ëìtrue를 ë°íí´ì¼ í©ëë¤. ê·¸ë ì§ ìì ê²½ì°ëfalse를 ë°íí´ì¼ í©ëë¤. - 기í ë±ë±(ìë ìì를 íµí´ ë ì´í´ë³´ê² ìµëë¤.)
ì´ ì¸ì ë¤ë¥¸ ì¡°ê±´ë ììµëë¤.
- íë½ì ê°ì²´ë¥¼ ëìì¼ë¡
[[GetPrototypeOf]]ê° ì ì©ëë©´ íë½ì ê°ì²´ì íê¹ ê°ì²´ì[[GetPrototypeOf]]를 ì ì©í ê²ê³¼ ëì¼í ê°ì´ ë°íëì´ì¼ í©ëë¤. íë½ìì íë¡í íì ì ì½ë ê²ì íê¹ ê°ì²´ì íë¡í íì ì ì½ë ê²ê³¼ ëì¼í´ì¼ íì£ .
í¸ë©ì´ ì°ì°ì ê°ë¡ì± ë ììì ì¸ê¸í ê·ì¹ì ë°ë¼ì¼ í©ëë¤.
ì´ì ê°ì ê·ì¹ì ìë°ì¤í¬ë¦½í¸ê° ì¼ê´ë ëìì íê³ ì못ë ëìì´ ìì¼ë©´ ì´ë¥¼ ê³ ì³ì£¼ë ìí ì í©ëë¤. ê·ì¹ 목ë¡ì ëª ì¸ììì íì¸í ì ììµëë¤. ì주 ì´ìí ì§ì íì§ ìëí ì´ ê·ì¹ì ì´ê¸¸ ì¼ì ê±°ì ììê²ëë¤.
ì, ì´ì 본격ì ì¼ë¡ ì¤ì©ì ì¸ ììë¤ì ì´í´ë³´ë©´ì íë½ì ê°ì²´ê° ì´ë»ê² ëìíëì§ ììë´ ìë¤.
get í¸ë©ì¼ë¡ íë¡í¼í° ê¸°ë³¸ê° ì¤ì í기
ê°ì¥ íí ë³¼ ì ìë í¸ë©ì íë¡í¼í°ë¥¼ ì½ê±°ë ì¸ ë ì¬ì©ëë í¸ë©ì ëë¤.
íë¡í¼í° ì½ê¸°ë¥¼ ê°ë¡ì±ë ¤ë©´ handlerì get(target, property, receiver) ë©ìëê° ìì´ì¼ í©ëë¤.
getë©ìëë íë¡í¼í°ë¥¼ ì½ì¼ë ¤ê³ í ë ìëí©ëë¤. ì¸ìë ë¤ìê³¼ ê°ìµëë¤.
targetâ ëìì ì ë¬í ê°ì²´ë¡new Proxyì 첫 ë²ì§¸ ì¸ìì ëë¤.propertyâ íë¡í¼í° ì´ë¦receiverâ íê¹ íë¡í¼í°ê° getterë¼ë©´receiverë getterê° í¸ì¶ë ëthisì ëë¤. ëê°ëproxyê°ì²´ ìì ì´thisê° ë©ëë¤. íë½ì ê°ì²´ë¥¼ ììë°ì ê°ì²´ê° ìë¤ë©´ í´ë¹ ê°ì²´ê°thisê° ë기ë íì£ . ì§ê¸ ë¹ì¥ì ì´ ì¸ìê° íì ìì¼ë¯ë¡ ë ìì¸í ë´ì©ì ëì¤ì ë¤ë£¨ëë¡ íê² ìµëë¤.
getì íì©í´ ê°ì²´ì 기본ê°ì ì¤ì í´ë³´ê² ìµëë¤.
ìììì ë§ë¤ ê²ì, ì¡´ì¬íì§ ìë ìì를 ì½ì¼ë ¤ê³ í ë ê¸°ë³¸ê° 0ì ë°íí´ì£¼ë ë°°ì´ì
ëë¤.
ì¡´ì¬íì§ ìë ìì를 ì½ì¼ë ¤ê³ íë©´ ë°°ì´ì ìë undefinedì ë°ííëë°, ìììì ë°°ì´(ê°ì²´)ì íë½ìë¡ ê°ì¸ì ì¡´ì¬íì§ ìë ìì(íë¡í¼í°)를 ì½ì¼ë ¤ê³ í ë 0ì´ ë°íëëë¡ í´ë³´ê² ìµëë¤.
let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
get(target, prop) {
if (prop in target) {
return target[prop];
} else {
return 0; // 기본ê°
}
}
});
alert( numbers[1] ); // 1
alert( numbers[123] ); // 0 (í´ë¹íë ììê° ë°°ì´ì ìì¼ë¯ë¡ 0ì´ ë°íë¨)
ìì를 íµí´ ì ì ìë¯ì´ getì ì¬ì©í´ í¸ë©ì ë§ëë ê±´ ìë¹í ì½ìµëë¤.
Proxy를 ì¬ì©íë©´ â기본â ê° ì¤ì ë¡ì§ì ìíë ëë¡ êµ¬íí ì ìì£ .
구ì ê³¼ ë²ìë¬¸ì´ ì ì¥ëì´ìë ì¬ì ì´ ìë¤ê³ ê°ì í´ë´ ìë¤.
let dictionary = {
'Hello': 'ìë
íì¸ì',
'Bye': 'ìë
í ê°ì¸ì'
};
alert( dictionary['Hello'] ); // ìë
íì¸ì
alert( dictionary['Welcome'] ); // undefined
ì§ê¸ ìíë¡ dictionaryì ìë 구ì ì ì ê·¼íë©´ undefinedê° ë°íë©ëë¤. ì¬ì ì ìë 구ì ì ê²ìíë ¤ íì ë undefinedê° ìë 구ì ê·¸ëë¡ë¥¼ ë°íí´ì£¼ë ê² ì¢ ë ëì ê² ê°ë¤ë ìê°ì´ ëë¤ì.
dictionary를 íë½ìë¡ ê°ì¸ì íë¡í¼í°ë¥¼ ì½ì¼ë ¤ê³ í ë ì´ë¥¼ íë½ìê° ê°ë¡ì±ëë¡ íë©´ ì°ë¦¬ê° ìíë 기ë¥ì 구íí ì ììµëë¤.
let dictionary = {
'Hello': 'ìë
íì¸ì',
'Bye': 'ìë
í ê°ì¸ì'
};
dictionary = new Proxy(dictionary, {
get(target, phrase) { // íë¡í¼í°ë¥¼ ì½ê¸°ë¥¼ ê°ë¡ì±ëë¤.
if (phrase in target) { // ì¡°ê±´: ì¬ì ì 구ì ì´ ìë ê²½ì°
return target[phrase]; // ë²ì문ì ë°íí©ëë¤
} else {
// 구ì ì´ ìë ê²½ì°ì 구ì ê·¸ëë¡ë¥¼ ë°íí©ëë¤.
return phrase;
}
}
});
// ì¬ì ì ê²ìí´ë´
ìë¤!
// ì¬ì ì ìë 구ì ì ì
ë ¥íë©´ ì
ë ¥ê°ì´ ê·¸ëë¡ ë°íë©ëë¤.
alert( dictionary['Hello'] ); // ìë
íì¸ì
alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (ì
ë ¥ê°ì´ ê·¸ëë¡ ì¶ë ¥ë¨)
íë½ì ê°ì²´ê° ë³ì를 ì´ë»ê² ë®ì´ì°ê³ ìëì§ ëì¬ê²¨ë³´ì기 ë°ëëë¤.
dictionary = new Proxy(dictionary, ...);
íê¹ ê°ì²´ì ìì¹ì ìê´ìì´ íë½ì ê°ì²´ë íê¹ ê°ì²´ë¥¼ ë®ì´ì¨ì¼ë§ í©ëë¤. ê°ì²´ë¥¼ íë½ìë¡ ê°ì¼ ì´íì ì ëë¡ íê¹ ê°ì²´ë¥¼ 참조íë ì½ëê° ìì´ì¼ í©ëë¤. ê·¸ë ì§ ìì¼ë©´ ìë§ì´ ë íë¥ ì´ ì주 ëìì§ëë¤.
set í¸ë©ì¼ë¡ íë¡í¼í° ê° ê²ì¦í기
ì«ìë§ ì ì¥í ì ìë ë°°ì´ì ë§ë¤ê³ ì¶ë¤ê³ ê°ì í´ë´ ìë¤. ì«ìíì´ ìë ê°ì ì¶ê°íë ¤ê³ íë©´ ìë¬ê° ë°ìíëë¡ í´ì¼ê² ì£ .
íë¡í¼í°ì ê°ì ì°ë ¤ê³ í ë ì´ë¥¼ ê°ë¡ì±ë set í¸ë©ì ì¬ì©í´ ì´ë¥¼ 구íí´ë³´ëë¡ íê² ìµëë¤. set ë©ìëì ì¸ìë ìëì ê°ì ìí ì í©ëë¤.
set(target, property, value, receiver):
targetâ ëìì ì ë¬í ê°ì²´ë¡new Proxyì 첫 ë²ì§¸ ì¸ìì ëë¤.propertyâ íë¡í¼í° ì´ë¦valueâ íë¡í¼í° ê°receiverâgetí¸ë©ê³¼ ì ì¬íê² ëìíë ê°ì²´ë¡, setter íë¡í¼í°ìë§ ê´ì¬í©ëë¤.
ì°ë¦¬ê° 구íí´ì¼ í set í¸ë©ì ì«ìí ê°ì ì¤ì íë ¤ í ëë§ true를, ê·¸ë ì§ ìì ê²½ì°ì(TypeErrorê° í¸ë¦¬ê±°ëê³ ) false를 ë°ííëë¡ í´ì¼ í©ëë¤.
set í¸ë©ì ì¬ì©í´ ë°°ì´ì ì¶ê°íë ¤ë ê°ì´ ì«ìíì¸ì§ ê²ì¦í´ë´
ìë¤.
let numbers = [];
numbers = new Proxy(numbers, { // (*)
set(target, prop, val) { // íë¡í¼í°ì ê°ì ì°ë ëìì ê°ë¡ì±ëë¤.
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
});
numbers.push(1); // ì¶ê°ê° ì±ê³µíìµëë¤.
numbers.push(2); // ì¶ê°ê° ì±ê³µíìµëë¤.
alert("Length is: " + numbers.length); // 2
numbers.push("test"); // Error: 'set' on proxy
alert("ìì¤ìì ìë¬ê° ë°ìí기 ë문ì ì´ ì¤ì ì ë ì¤íëì§ ììµëë¤.");
ë°°ì´ ê´ë ¨ 기ë¥ë¤ì ì¬ì í ì¬ì©í ì ìë¤ë ì ì 주목í´ì£¼ì기 ë°ëëë¤. push를 ì¬ì©í´ ë°°ì´ì ìë¡ì´ ìì를 ì¶ê°íê³ length íë¡í¼í°ë ì´ë¥¼ ì ë°ìíê³ ìë¤ë ê²ì íµí´ ì´ë¥¼ íì¸í ì ìììµëë¤. íë½ì를 ì¬ì©í´ë 기존ì ìë 기ë¥ì ì ëë¡ ììëì§ ììµëë¤.
pushë unshift ê°ì´ ë°°ì´ì ê°ì ì¶ê°í´ì£¼ë ë©ìëë¤ì ë´ë¶ìì [[Set]]ì ì¬ì©íê³ ì기 ë문ì ë©ìë를 ì¤ë²ë¼ì´ë íì§ ììë íë½ìê° ëìì ê°ë¡ì±ê³ ê°ì ê²ì¦í´ì¤ëë¤.
ì½ëê° ê¹¨ëíê³ ê°ê²°í´ì§ë í¨ê³¼ê° ìì£ .
true를 ìì§ ë§ê³ ë°íí´ì£¼ì¸ì.ììì ì¸ê¸íë¯ì´ ê¼ ì§ì¼ì¼ í ê·ì¹ì´ ììµëë¤.
set í¸ë©ì ì¬ì©í ë ê°ì ì°ë ê² ì±ê³µíì ë ë°ëì true를 ë°íí´ì¤ì¼ í©ëë¤.
true를 ë°ííì§ ììê±°ë falsyí ê°ì ë°ííê² ëë©´ TypeErrorê° ë°ìí©ëë¤.
ownKeysì getOwnPropertyDescriptorë¡ ë°ë³µ ìì í기
Object.keys, for..in ë°ë³µë¬¸ì ë¹ë¡¯í íë¡í¼í° ìí ê´ë ¨ ë©ìë ëë¤ìë ë´ë¶ ë©ìë [[OwnPropertyKeys]](í¸ë© ë©ìëë ownKeysì)를 ì¬ì©í´ íë¡í¼í° 목ë¡ì ì»ìµëë¤.
ê·¸ë°ë° ì¸ë¶ ëì ë°©ìì ì°¨ì´ê° ììµëë¤.
Object.getOwnPropertyNames(obj)â ì¬ë³¼íì´ ìë í¤ë§ ë°íí©ëë¤.Object.getOwnPropertySymbols(obj)â ì¬ë³¼í í¤ë§ ë°íí©ëë¤.Object.keys/values()âenumerableíëê·¸ê°trueì´ë©´ì ì¬ë³¼íì´ ìë í¤ë ì¬ë³¼íì´ ìë í¤ì í´ë¹íë ê° ì 체를 ë°íí©ëë¤(íë¡í¼í° íëê·¸ì ê´í ë´ì©ì íë¡í¼í° íëê·¸ì ì¤ëª ììì ì°¾ìë³´ì¤ ì ììµëë¤).for..inë°ë³µë¬¸ âenumerableíëê·¸ê°trueì¸ ì¬ë³¼íì´ ìë í¤, íë¡í íì í¤ë¥¼ ìíí©ëë¤.
ë©ìëë§ë¤ ì°¨ì´ë ìì§ë§ [[OwnPropertyKeys]]를 íµí´ íë¡í¼í° 목ë¡ì ì»ëë¤ë ì ì ëì¼í©ëë¤.
ìë ìììì ownKeys í¸ë©ì ì¬ì©í´ _ë¡ ììíë íë¡í¼í°ë for..in ë°ë³µë¬¸ì ìí ëììì ì ì¸íëë¡ í´ë³´ììµëë¤. ownKeys를 ì¬ì©í기 ë문ì Object.keysì Object.valuesìë ëì¼í ë¡ì§ì´ ì ì©ëë ê²ì íì¸í ì ììµëë¤.
let user = {
name: "John",
age: 30,
_password: "***"
};
user = new Proxy(user, {
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});
// "ownKeys" í¸ë©ì _password를 ê±´ëëëë¤.
for(let key in user) alert(key); // name, age
// ìë ë ë©ìëìë ëì¼í ë¡ì§ì´ ì ì©ë©ëë¤.
alert( Object.keys(user) ); // name,age
alert( Object.values(user) ); // John,30
ì§ê¸ê¹ì§ ìëí ëë¡ ììê° ì ëìíë¤ì.
ê·¸ë°ë° ê°ì²´ ë´ì ì¡´ì¬íì§ ìë í¤ë¥¼ ë°ííë ¤ê³ íë©´ Object.keysë ì´ í¤ë¥¼ ì ëë¡ ë³´ì¬ì£¼ì§ ììµëë¤.
let user = { };
user = new Proxy(user, {
ownKeys(target) {
return ['a', 'b', 'c'];
}
});
alert( Object.keys(user) ); // <ë¹ ë¬¸ìì´>
ì´ì ê° ë¬´ìì¼ê¹ì? ëµì ê°ë¨í©ëë¤. Object.keysë enumerable íëê·¸ê° ìë íë¡í¼í°ë§ ë°íí기 ë문ì´ì£ . ì´ë¥¼ íì¸í기 ìí´ Object.keysë ë´ë¶ ë©ìëì¸ [[GetOwnProperty]]를 í¸ì¶í´ 모ë íë¡í¼í°ì ì¤ëª
ì를 íì¸í©ëë¤. ì ììì íë¡í¼í°ë ì¤ëª
ìê° íëë ìê³ enumerable íëê·¸ë ìì¼ë¯ë¡ ìí ëììì ì ì¸ëë ê²ì´ì£ .
Object.keys í¸ì¶ ì íë¡í¼í°ë¥¼ ë°ííê² íë ¤ë©´ enumerable íë그를 ë¶ì¬ì¤ íë¡í¼í°ê° ê°ì²´ì ì¡´ì¬íëë¡ íê±°ë [[GetOwnProperty]]ê° í¸ì¶ë ë ì´ë¥¼ ì¤ê°ìì ê°ë¡ì±ì ì¤ëª
ì enumerable: true를 ë°ííê² í´ì£¼ë©´ ë©ëë¤. getOwnPropertyDescriptor í¸ë©ì´ ë°ë¡ ì´ë ì¬ì©ëì£ .
ìì를 ì´í´ë´ ìë¤.
let user = { };
user = new Proxy(user, {
ownKeys(target) { // íë¡í¼í° 리ì¤í¸ë¥¼ ì»ì ë ë± í ë² í¸ì¶ë©ëë¤.
return ['a', 'b', 'c'];
},
getOwnPropertyDescriptor(target, prop) { // 모ë íë¡í¼í°ë¥¼ ëìì¼ë¡ í¸ì¶ë©ëë¤.
return {
enumerable: true,
configurable: true
/* ì´ ì¸ì íëê·¸ë ë°íí ì ììµëë¤. "value:..."ë ê°ë¥í©ëë¤. */
};
}
});
alert( Object.keys(user) ); // a, b, c
ê°ì²´ì íë¡í¼í°ê° ìì ë [[GetOwnProperty]]ë§ ê°ë¡ì±ë©´ ëë¤ë ì ì ë¤ì íë² ì기íì기 ë°ëëë¤.
deletePropertyì ì¬ë¬ í¸ë©ì ì¬ì©í´ íë¡í¼í° ë³´í¸í기
_(ë°ì¤)ì´ ìì ë¶ì íë¡í¼í°ë ë©ìëë ë´ë¶ì©ì¼ë¡ë§ ì°ëë¡ íë 컨벤ì
ì ë리 ì¬ì©ëê³ ìë 컨벤ì
ì¤ íëì
ëë¤. _ì´ ìì ë¶ì¼ë©´ ê°ì²´ ë°ê¹¥ìì ì´ íë¡í¼í°ì ì ê·¼í´ì ì ë©ëë¤.
ê·¸ë°ë° 기ì ì ì¼ë¡ ê°ë¥íì£ .
let user = {
name: "John",
_password: "ë¹ë°"
};
alert(user._password); // ë¹ë°
íë½ì를 ì¬ì©í´ _ë¡ ììíë íë¡í¼í°ì ì ê·¼íì§ ëª»íëë¡ ë§ìë´
ìë¤.
ìíë 기ë¥ì 구ííë ¤ë©´ ìëì ê°ì í¸ë©ì´ íìí©ëë¤.
getâ íë¡í¼í°ë¥¼ ì½ì¼ë ¤ê³ íë©´ ìë¬ë¥¼ ëì ¸ì¤setâ íë¡í¼í°ì ì°ë ¤ê³ íë©´ ìë¬ë¥¼ ëì ¸ì¤deletePropertyâ íë¡í¼í°ë¥¼ ì§ì°ë ¤ê³ íë©´ ìë¬ë¥¼ ëì ¸ì¤ownKeysâfor..inì´ëObject.keysê°ì íë¡í¼í° ìí ë©ìë를 ì¬ì©í ë_ë¡ ììíë ë©ìëë ì ì¸í¨
구í ê²°ê³¼ë ë¤ìê³¼ ê°ìµëë¤.
let user = {
name: "John",
_password: "***"
};
user = new Proxy(user, {
get(target, prop) {
if (prop.startsWith('_')) {
throw new Error("ì ê·¼ì´ ì íëì´ììµëë¤.");
}
let value = target[prop];
return (typeof value === 'function') ? value.bind(target) : value; // (*)
},
set(target, prop, val) { // íë¡í¼í° ì°ê¸°ë¥¼ ê°ë¡ì±ëë¤.
if (prop.startsWith('_')) {
throw new Error("ì ê·¼ì´ ì íëì´ììµëë¤.");
} else {
target[prop] = val;
return true;
}
},
deleteProperty(target, prop) { // íë¡í¼í° ìì 를 ê°ë¡ì±ëë¤.
if (prop.startsWith('_')) {
throw new Error("ì ê·¼ì´ ì íëì´ììµëë¤.");
} else {
delete target[prop];
return true;
}
},
ownKeys(target) { // íë¡í¼í° ìí를 ê°ë¡ì±ëë¤.
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});
// "get" í¸ë©ì´ _password ì½ê¸°ë¥¼ ë§ìµëë¤.
try {
alert(user._password); // Error: ì ê·¼ì´ ì íëì´ììµëë¤.
} catch(e) { alert(e.message); }
// "set" í¸ë©ì´ _passwordì ê°ì ì°ëê²ì ë§ìµëë¤.
try {
user._password = "test"; // Error: ì ê·¼ì´ ì íëì´ììµëë¤.
} catch(e) { alert(e.message); }
// "deleteProperty" í¸ë©ì´ _password ìì 를 ë§ìµëë¤.
try {
delete user._password; // Error: ì ê·¼ì´ ì íëì´ììµëë¤.
} catch(e) { alert(e.message); }
// "ownKeys" í¸ë©ì´ ìí ëììì _password를 ì ì¸ìíµëë¤.
for(let key in user) alert(key); // name
get í¸ë©ì (*)ë¡ íìí ì¤ì ëì¬ê²¨ ë´ì£¼ì기 ë°ëëë¤.
get(target, prop) {
// ...
let value = target[prop];
return (typeof value === 'function') ? value.bind(target) : value; // (*)
}
í¨ìì¸ì§ ì¬ë¶ë¥¼ íì¸íì¬ value.bind(target)를 í¸ì¶ íê³ ìë¤ì. ìê·¸ë´ê¹ì?
ì´ì ë user.checkPassword()ê°ì ê°ì²´ ë©ìëê° _passwordì ì ê·¼í ì ìëë¡ í´ì£¼ê¸° ìí´ìì
ëë¤.
user = {
// ...
checkPassword(value) {
// checkPassword(ë¹ë°ë²í¸ íì¸)ë _password를 ì½ì ì ìì´ì¼ í©ëë¤.
return value === this._password;
}
}
user.checkPassword()를 í¸ì¶íë©´ ì ìì ê°ì²´ê° thisê° ëë¯ë¡ íë½ìë¡ ê°ì¼ userì ì ê·¼íê² ëëë°, this._passwordë get í¸ë©(íë¡í¼í°ë¥¼ ì½ì¼ë ¤ê³ íë©´ ëìí¨)ì íì±ííë¯ë¡ ìë¬ê° ëì ¸ì§ëë¤.
(*)ë¡ íìí ì¤ìì ê°ì²´ ë©ìëì 컨í
ì¤í¸ë¥¼ ì본 ê°ì²´ì¸ targetì ë°ì¸ë©ìì¼ì¤ ì´ì ê° ë°ë¡ ì¬ê¸°ì ììµëë¤. checkPassword()를 í¸ì¶í ë ì¸ì ë í¸ë© ìì´ targetì´ thisê° ëê² í기 ìí´ìì´ì£ .
ì´ ë°©ë²ì ëë¶ë¶ ì ìëí긴 íëë° ë©ìëê° ì´ëê°ìì íë½ìë¡ ê°ì¸ì§ ìì ê°ì²´ë¥¼ ëê¸°ê² ëë©´ ìë§ì§ì°½ì´ ëì´ë²ë¦¬ê¸° ë문ì ì´ìì ì¸ ë°©ë²ì ìëëë¤. 기존 ê°ì²´ì íë½ìë¡ ê°ì¼ ê°ì²´ê° ì´ëì ìëì§ íì í ì ì기 ë문ì´ì£ .
í ê°ì²´ë¥¼ ì¬ë¬ ë² íë½ìë¡ ê°ì ê²½ì° ê° íë½ìë§ë¤ ê°ì²´ì ê°íë 'ìì âì´ ë¤ë¥¼ ì ìë¤ë ì ëí 문ì ì ëë¤. íë½ìë¡ ê°ì¸ì§ ìì ê°ì²´ë¥¼ ë©ìëì ë기ë ê²½ì°ì²ë¼ ììì¹ ìì ê²°ê³¼ê° ëíë ì ììµëë¤.
ë°ë¼ì ì´ë° ííì íë½ìë ì´ëìë ì¬ì©í´ì ì ë©ëë¤.
모ë ìë°ì¤í¬ë¦½í¸ ìì§ì í´ëì¤ ë´ private íë¡í¼í°ë¥¼ ì¬ì©í ì ìê² í´ì¤ëë¤. private íë¡í¼í°ë íë¡í¼í° ìì #ì ë¶ì´ë©´ ë§ë¤ ì ìëë°, ìì¸í ë´ì©ì private, protected íë¡í¼í°ì ë©ìëìì ì°¾ìë³¼ ì ììµëë¤. private íë¡í¼í°ë¥¼ ì¬ì©íë©´ íë½ì ìì´ë íë¡í¼í°ë¥¼ ë³´í¸í ì ììµëë¤.
ê·¸ë°ë° private íë¡í¼í°ë ììì´ ë¶ê°ë¥íë¤ë ë¨ì ì´ ììµëë¤.
has í¸ë©ì¼ë¡ âë²ìâ ë´ ì¬ë¶ íì¸í기
ì¢ ë ë§ì ìì를 ì´í´ë´ ìë¤.
ë²ì를 ë´ê³ ìë ê°ì²´ê° ììµëë¤.
let range = {
start: 1,
end: 10
};
in ì°ì°ì를 ì¬ì©í´ í¹ì ì«ìê° range ë´ì ìëì§ íì¸í´ë´
ìë¤.
has í¸ë©ì in í¸ì¶ì ê°ë¡ì±ëë¤.
has(target, property)
targetânew Proxyì 첫 ë²ì§¸ ì¸ìë¡ ì ë¬ëë íê¹ ê°ì²´propertyâ íë¡í¼í° ì´ë¦
ìì:
let range = {
start: 1,
end: 10
};
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end;
}
});
alert(5 in range); // true
alert(50 in range); // false
ì ë§ ë©ì§ í¸ì 문ë²ì´ì§ ìëì? 구íë ì주 ê°ë¨í©ëë¤.
apply í¸ë©ì¼ë¡ í¨ì ê°ì¸ê¸°
í¨ì ìì íë½ìë¡ ê°ì ì ììµëë¤.
apply(target, thisArg, args) í¸ë©ì íë½ì를 í¨ìì²ë¼ í¸ì¶íë ¤ê³ í ë ëìí©ëë¤.
targetâ íê¹ ê°ì²´(ìë°ì¤í¬ë¦½í¸ìì í¨ìë ê°ì²´ì)thisArgâthisì ê°argsâ ì¸ì 목ë¡
call/applyì ë°ì½ë ì´í°, í¬ìë©ìì ì´í´ë³´ìë delay(f, ms) ë°ì½ë ì´í°(decorator)를 ë ì¬ë ¤ë´
ìë¤.
í´ë¹ ì±í° ìì íë½ì를 ì¬ì©íì§ ìê³ ë°ì½ë ì´í°ë¥¼ 구ííììµëë¤. delay(f, ms)를 í¸ì¶íë©´ í¨ìê° ë°íëëë°, ì´ í¨ìë í¨ì fê° msë°ë¦¬ì´ íì í¸ì¶ëëë¡ í´ì£¼ìì£ .
í¨ì를 기ë°ì¼ë¡ ìì±íë ë°ì½ë ì´í°ë ë¤ìê³¼ ê°ìµëë¤.
function delay(f, ms) {
// ì§ì í ìê°ì´ í른 ë¤ìì f í¸ì¶ì ì ë¬í´ì£¼ë ëí¼ í¨ì를 ë°íí©ëë¤.
return function() { // (*)
setTimeout(() => f.apply(this, arguments), ms);
};
}
function sayHi(user) {
alert(`Hello, ${user}!`);
}
// ëí¼ í¨ìë¡ ê°ì¼ ë¤ìì sayHi를 í¸ì¶íë©´ 3ì´ í í¨ìê° í¸ì¶ë©ëë¤.
sayHi = delay(sayHi, 3000);
sayHi("John"); // Hello, John! (3ì´ í)
ì´ë¯¸ ì´í´ë´¤ë¯ì´ ì´ ë°ì½ë ì´í°ë ëë¶ë¶ì ê²½ì° ì ëìí©ëë¤. (*)ë¡ íì íê³³ì ëí¼ í¨ìë ì¼ì ìê° í í¨ì를 í¸ì¶í ì ìê² í´ì£¼ì£ .
ê·¸ë°ë° ëí¼ í¨ìë íë¡í¼í° ì½ê¸°/ì°ê¸° ë±ì ì°ì°ì ì ë¬í´ì£¼ì§ 못í©ëë¤. ëí¼ í¨ìë¡ ê°ì¸ê³ ë ë¤ìì 기존 í¨ìì íë¡í¼í°(name, length ë±) ì ë³´ê° ì¬ë¼ì§ëë¤.
function delay(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms);
};
}
function sayHi(user) {
alert(`Hello, ${user}!`);
}
alert(sayHi.length); // 1 (í¨ì ì ìë¶ìì ëª
ìí ì¸ìì ê°ì)
sayHi = delay(sayHi, 3000);
alert(sayHi.length); // 0 (ëí¼ í¨ì ì ìë¶ì ì¸ìê° ìì)
Proxy ê°ì²´ë íê¹ ê°ì²´ì 모ë ê²ì ì ë¬í´ì£¼ë¯ë¡ í¨ì¬ ê°ë ¥í©ëë¤.
ëí¼ í¨ì ëì Proxy를 ì¬ì©í´ë´
ìë¤.
function delay(f, ms) {
return new Proxy(f, {
apply(target, thisArg, args) {
setTimeout(() => target.apply(thisArg, args), ms);
}
});
}
function sayHi(user) {
alert(`Hello, ${user}!`);
}
sayHi = delay(sayHi, 3000);
alert(sayHi.length); // 1 (*) íë½ìë "get length" ì°ì°ê¹ì§ íê¹ ê°ì²´ì ì ë¬í´ì¤ëë¤.
sayHi("John"); // Hello, John! (3ì´ í)
ê²°ê³¼ë ê°ì§ë§ ì´ë²ì í¸ì¶ë¿ë§ ìëë¼ íë½ìì ê°íë 모ë ì°ì°ì´ ì본 í¨ìì ì ë¬ë ê²ì íì¸í ì ììµëë¤. ì본 í¨ì를 íë½ìë¡ ê°ì¼ ì´íì (*)ë¡ íìí ì¤ìì sayHi.lengthê° ì ëë¡ ë 결과를 ë°ííê³ ìë ê²ì íì¸í ì ììµëë¤.
ì¢ ë ì±ë¥ì´ ì¢ì ëí¼ë¥¼ ê°ê² ëìë¤ì.
ì´ ì¸ìë ë¤ìí í¸ë©ì´ ì¡´ì¬í©ëë¤. í¸ë© ì ì²´ 리ì¤í¸ë ì쪽 íì ì 리ëì´ìì¼ë íì¸íìë©´ ë©ëë¤. ì§ê¸ê¹ì§ ìê°í´ ë린 ìì를 ìì©íë©´ ì¶©ë¶í íë½ì를 íì©íì¤ ì ìì ê²ëë¤.
Reflect
Reflect is a built-in object that simplifies creation of Proxy.
It was said previously that internal methods, such as [[Get]], [[Set]] and others are specification-only, they canât be called directly.
The Reflect object makes that somewhat possible. Its methods are minimal wrappers around the internal methods.
Here are examples of operations and Reflect calls that do the same:
| Operation | Reflect call |
Internal method |
|---|---|---|
obj[prop] |
Reflect.get(obj, prop) |
[[Get]] |
obj[prop] = value |
Reflect.set(obj, prop, value) |
[[Set]] |
delete obj[prop] |
Reflect.deleteProperty(obj, prop) |
[[Delete]] |
new F(value) |
Reflect.construct(F, value) |
[[Construct]] |
| ⦠| ⦠| ⦠|
For example:
let user = {};
Reflect.set(user, 'name', 'John');
alert(user.name); // John
In particular, Reflect allows us to call operators (new, deleteâ¦) as functions (Reflect.construct, Reflect.deleteProperty, â¦). Thatâs an interesting capability, but here another thing is important.
For every internal method, trappable by Proxy, thereâs a corresponding method in Reflect, with the same name and arguments as the Proxy trap.
So we can use Reflect to forward an operation to the original object.
In this example, both traps get and set transparently (as if they didnât exist) forward reading/writing operations to the object, showing a message:
let user = {
name: "John",
};
user = new Proxy(user, {
get(target, prop, receiver) {
alert(`GET ${prop}`);
return Reflect.get(target, prop, receiver); // (1)
},
set(target, prop, val, receiver) {
alert(`SET ${prop}=${val}`);
return Reflect.set(target, prop, val, receiver); // (2)
}
});
let name = user.name; // shows "GET name"
user.name = "Pete"; // shows "SET name=Pete"
Here:
Reflect.getreads an object property.Reflect.setwrites an object property and returnstrueif successful,falseotherwise.
That is, everythingâs simple: if a trap wants to forward the call to the object, itâs enough to call Reflect.<method> with the same arguments.
In most cases we can do the same without Reflect, for instance, reading a property Reflect.get(target, prop, receiver) can be replaced by target[prop]. There are important nuances though.
Proxying a getter
Letâs see an example that demonstrates why Reflect.get is better. And weâll also see why get/set have the third argument receiver, that we didnât use before.
We have an object user with _name property and a getter for it.
Hereâs a proxy around it:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop];
}
});
alert(userProxy.name); // Guest
The get trap is âtransparentâ here, it returns the original property, and doesnât do anything else. Thatâs enough for our example.
Everything seems to be all right. But letâs make the example a little bit more complex.
After inheriting another object admin from user, we can observe the incorrect behavior:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) {
return target[prop]; // (*) target = user
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
// Expected: Admin
alert(admin.name); // outputs: Guest (?!?)
Reading admin.name should return "Admin", not "Guest"!
Whatâs the matter? Maybe we did something wrong with the inheritance?
But if we remove the proxy, then everything will work as expected.
The problem is actually in the proxy, in the line (*).
-
When we read
admin.name, asadminobject doesnât have such own property, the search goes to its prototype. -
The prototype is
userProxy. -
When reading
nameproperty from the proxy, itsgettrap triggers and returns it from the original object astarget[prop]in the line(*).A call to
target[prop], whenpropis a getter, runs its code in the contextthis=target. So the result isthis._namefrom the original objecttarget, that is: fromuser.
To fix such situations, we need receiver, the third argument of get trap. It keeps the correct this to be passed to a getter. In our case thatâs admin.
How to pass the context for a getter? For a regular function we could use call/apply, but thatâs a getter, itâs not âcalledâ, just accessed.
Reflect.get can do that. Everything will work right if we use it.
Hereâs the corrected variant:
let user = {
_name: "Guest",
get name() {
return this._name;
}
};
let userProxy = new Proxy(user, {
get(target, prop, receiver) { // receiver = admin
return Reflect.get(target, prop, receiver); // (*)
}
});
let admin = {
__proto__: userProxy,
_name: "Admin"
};
alert(admin.name); // Admin
Now receiver that keeps a reference to the correct this (that is admin), is passed to the getter using Reflect.get in the line (*).
We can rewrite the trap even shorter:
get(target, prop, receiver) {
return Reflect.get(...arguments);
}
Reflect calls are named exactly the same way as traps and accept the same arguments. They were specifically designed this way.
So, return Reflect... provides a safe no-brainer to forward the operation and make sure we donât forget anything related to that.
Proxy limitations
Proxies provide a unique way to alter or tweak the behavior of the existing objects at the lowest level. Still, itâs not perfect. There are limitations.
Built-in objects: Internal slots
Many built-in objects, for example Map, Set, Date, Promise and others make use of so-called âinternal slotsâ.
These are like properties, but reserved for internal, specification-only purposes. For instance, Map stores items in the internal slot [[MapData]]. Built-in methods access them directly, not via [[Get]]/[[Set]] internal methods. So Proxy canât intercept that.
Why care? Theyâre internal anyway!
Well, hereâs the issue. After a built-in object like that gets proxied, the proxy doesnât have these internal slots, so built-in methods will fail.
For example:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1); // Error
Internally, a Map stores all data in its [[MapData]] internal slot. The proxy doesnât have such a slot. The built-in method Map.prototype.set method tries to access the internal property this.[[MapData]], but because this=proxy, canât find it in proxy and just fails.
Fortunately, thereâs a way to fix it:
let map = new Map();
let proxy = new Proxy(map, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
proxy.set('test', 1);
alert(proxy.get('test')); // 1 (works!)
Now it works fine, because get trap binds function properties, such as map.set, to the target object (map) itself.
Unlike the previous example, the value of this inside proxy.set(...) will be not proxy, but the original map. So when the internal implementation of set tries to access this.[[MapData]] internal slot, it succeeds.
Array has no internal slotsA notable exception: built-in Array doesnât use internal slots. Thatâs for historical reasons, as it appeared so long ago.
So thereâs no such problem when proxying an array.
Private fields
A similar thing happens with private class fields.
For example, getName() method accesses the private #name property and breaks after proxying:
class User {
#name = "Guest";
getName() {
return this.#name;
}
}
let user = new User();
user = new Proxy(user, {});
alert(user.getName()); // Error
The reason is that private fields are implemented using internal slots. JavaScript does not use [[Get]]/[[Set]] when accessing them.
In the call getName() the value of this is the proxied user, and it doesnât have the slot with private fields.
Once again, the solution with binding the method makes it work:
class User {
#name = "Guest";
getName() {
return this.#name;
}
}
let user = new User();
user = new Proxy(user, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
alert(user.getName()); // Guest
That said, the solution has drawbacks, as explained previously: it exposes the original object to the method, potentially allowing it to be passed further and breaking other proxied functionality.
Proxy != target
The proxy and the original object are different objects. Thatâs natural, right?
So if we use the original object as a key, and then proxy it, then the proxy canât be found:
let allUsers = new Set();
class User {
constructor(name) {
this.name = name;
allUsers.add(this);
}
}
let user = new User("John");
alert(allUsers.has(user)); // true
user = new Proxy(user, {});
alert(allUsers.has(user)); // false
As we can see, after proxying we canât find user in the set allUsers, because the proxy is a different object.
===Proxies can intercept many operators, such as new (with construct), in (with has), delete (with deleteProperty) and so on.
But thereâs no way to intercept a strict equality test for objects. An object is strictly equal to itself only, and no other value.
So all operations and built-in classes that compare objects for equality will differentiate between the object and the proxy. No transparent replacement here.
Revocable proxies
A revocable proxy is a proxy that can be disabled.
Letâs say we have a resource, and would like to close access to it any moment.
What we can do is to wrap it into a revocable proxy, without any traps. Such a proxy will forward operations to object, and we can disable it at any moment.
The syntax is:
let {proxy, revoke} = Proxy.revocable(target, handler)
The call returns an object with the proxy and revoke function to disable it.
Hereâs an example:
let object = {
data: "Valuable data"
};
let {proxy, revoke} = Proxy.revocable(object, {});
// pass the proxy somewhere instead of object...
alert(proxy.data); // Valuable data
// later in our code
revoke();
// the proxy isn't working any more (revoked)
alert(proxy.data); // Error
A call to revoke() removes all internal references to the target object from the proxy, so they are no longer connected. The target object can be garbage-collected after that.
We can also store revoke in a WeakMap, to be able to easily find it by a proxy object:
let revokes = new WeakMap();
let object = {
data: "Valuable data"
};
let {proxy, revoke} = Proxy.revocable(object, {});
revokes.set(proxy, revoke);
// ..later in our code..
revoke = revokes.get(proxy);
revoke();
alert(proxy.data); // Error (revoked)
The benefit of such an approach is that we donât have to carry revoke around. We can get it from the map by proxy when needed.
We use WeakMap instead of Map here because it wonât block garbage collection. If a proxy object becomes âunreachableâ (e.g. no variable references it any more), WeakMap allows it to be wiped from memory together with its revoke that we wonât need any more.
References
Summary
Proxy is a wrapper around an object, that forwards operations on it to the object, optionally trapping some of them.
It can wrap any kind of object, including classes and functions.
The syntax is:
let proxy = new Proxy(target, {
/* traps */
});
â¦Then we should use proxy everywhere instead of target. A proxy doesnât have its own properties or methods. It traps an operation if the trap is provided, otherwise forwards it to target object.
We can trap:
- Reading (
get), writing (set), deleting (deleteProperty) a property (even a non-existing one). - Calling a function (
applytrap). - The
newoperator (constructtrap). - Many other operations (the full list is at the beginning of the article and in the docs).
That allows us to create âvirtualâ properties and methods, implement default values, observable objects, function decorators and so much more.
We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality.
The Reflect API is designed to complement Proxy. For any Proxy trap, thereâs a Reflect call with same arguments. We should use those to forward calls to target objects.
Proxies have some limitations:
- Built-in objects have âinternal slotsâ, access to those canât be proxied. See the workaround above.
- The same holds true for private class fields, as they are internally implemented using slots. So proxied method calls must have the target object as
thisto access them. - Object equality tests
===canât be intercepted. - Performance: benchmarks depend on an engine, but generally accessing a property using a simplest proxy takes a few times longer. In practice that only matters for some âbottleneckâ objects though.
ëê¸
<code>í그를, ì¬ë¬ ì¤ë¡ 구ì±ë ì½ë를 ì½ì íê³ ì¶ë¤ë©´<pre>í그를 ì´ì©íì¸ì. 10ì¤ ì´ìì ì½ëë plnkr, JSBin, codepen ë±ì ìëë°ì¤ë¥¼ ì¬ì©íì¸ì.