π» Proxyλ?
Proxy
λ κ°μ²΄λ₯Ό κ°μΈ κ°μ²΄μ λν κΈ°λ³Έ μμ
μ κ°λ‘μ±κ³ μ¬μ μνλ κ°μ²΄μ
λλ€.
κ°λ‘μ±μ§ μμ
μ μμ μΌμͺ½κ³Ό κ·Έλ¦Όκ³Ό κ°μ΄ μλ κ°μ²΄κ° μ²λ¦¬νλλ‘ κ·Έλλ‘ μ λ¬ λκ±°λ, μ€λ₯Έμͺ½ κ·Έλ¦Όκ³Ό κ°μ΄ Proxy
κ°μ²΄ μ체μμ μ²λ¦¬λκΈ°λ ν©λλ€.
Proxy
λ₯Ό νμ©ν λνμ μΈ νλ μμν¬λ Vue3
λ‘, reactivity
λ₯Ό ꡬννκΈ°μν΄ Proxy
λ₯Ό μ¬μ©νμ΅λλ€.
π» Proxy μμ±
1
const proxiedObject = new Proxy(target, handler);
μ μμμ²λΌ λ κ°μ 맀κ°λ³μλ₯Ό μ¬μ©νμ¬ Proxy
κ°μ²΄λ₯Ό μ μΈν©λλ€.
target
: νλ‘μν (κ°μΈκ²λ ) μλ³Έ κ°μ²΄λ‘, ν¨μλ₯Ό ν¬ν¨ν λͺ¨λ κ°μ²΄κ° κ°λ₯ν©λλ€.handler
: κ°λ‘μ±λ μμ κ³Ό κ·Έ μμ μ μ¬μ μ(trap)ν κ²λ€μ λͺ¨μλμ κ°μ²΄μ λλ€.
Proxy
κ°μ²΄μ μμ
μ΄ κ°ν΄μ‘μ λ, handler
μ κ·Έμ μμνλ μμ
(trap)μ΄ μμΌλ©΄ Proxy
κ°μ²΄κ° μμ
μ μ²λ¦¬νκ²λκ³ μλ€λ©΄ μλ³Έκ°μ²΄ (target
)κ° ν΄λΉ μμ
μ μννκ² λ©λλ€.
1
2
3
4
5
6
7
8
9
const target: Record<string, any> = {};
const proxiedObject = new Proxy(target, {});
proxiedObject.message1 = "Hello";
console.log(target.message1); // Hello
console.log(target.message1); // Hello
μ μμ λ³Ό μ μλ―μ΄ ProxiedObject
μ message1 νλ‘νΌν°λ₯Ό μΆκ°ν μμ
μ΄, target
μλ μ μ©λμμμ λ³Ό μ μμ΅λλ€.
μ΄λ Proxy
κ°μ²΄μ λΉ handler
κ° μ λ¬λμμμΌλ‘, Proxy
κ°μ²΄μ κ°ν΄μ§ μμ
λ€μ λͺ¨λ μλ³Έκ°μ²΄λ‘ μ λ¬λμκΈ° λλ¬Έμ
λλ€.
π» Handler Methods
Proxy
κ°μ²΄μ trap
μ κ°μ²΄μ λ΄λΆ 맀μλ νΈμΆμ κ°λ‘μ±λλ€. Proxy
κ° κ°λ‘μ±λ λ΄λΆ λ©μλ 리μ€νΈλ μλμ κ°μ΅λλ€.
Internal Method | Handler Method | λμ μμ |
---|---|---|
[[GetPrototypeOf]] | getPrototypeOf | Object.getPrototypeOf νΈμΆ μ |
[[SetPrototypeOf]] | setPrototypeOf | Object.setPrototypeOf νΈμΆ μ |
[[isExtensible]] | isExtensible | Object.isExtensible νΈμΆ μ |
[[preventExtension]] | preventExtensions | Object.preventExtensions νΈμΆ μ |
[[GetOwnProperty]] | getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor for..in Object.[keys | values | entries] νΈμΆ μ |
[[DefineOwnProperty]] | defineProperty | Object.defineProperty νΈμΆ μ |
[[HasProperty]] | has | in μ°μ°μ μ¬μ©μ |
[[Get]] | get | νλ‘νΌν°λ₯Ό μ½μ λ |
[[Set]] | set | νλ‘νΌν°λ₯Ό μΈ λ |
[[Delete]] | deleteProperty | delete μ°μ°μ μ¬μ© μ |
[[OwnPropertyKeys]] | ownKeys | Object.getOwnPropertyNames , Object.getOwnPropertySymbols , for..in Object.[keys | values | entries] νΈμΆ μ |
[[Call]] | apply | ν¨μλ₯Ό νΈμΆν λ |
[[Constructor]] | constructor | new μ°μ°μκ° μ¬μ© μ |
π» [[Get]]
κ°μ₯ ννκ² μ¬μ©λλ Proxy handler
μ νΈλ©μΌλ‘ νλ‘νΌν°λ₯Ό μ½μ λ μ¬μ©λ©λλ€.
get
νΈλ©μ μ¬μ©νκΈ° μν΄μλ handler
μ get
λ©μλκ° μμ΄μΌ ν©λλ€.
1
function get(target: T, property: string | symbol, receiver: any): any;
- target: λμμ μ λ¬ν λμκ°μ²΄ (μλ³Έκ°μ²΄) μ λλ€.
- property: κ°μ Έμ¬ νλ‘νΌν°μ μ΄λ¦λλ
Symbol
μ λλ€. - receiver:
Proxy
λλProxy
μμ μμλλ κ°μ²΄λ‘get
μ΄ λμν λthis
λ₯Ό μλ―Έν©λλ€. 보ν΅Proxy
κ°μ²΄κ°this
κ° λμ§λ§, λ§μ½Proxy
κ°μ²΄λ₯Ό μμλ°μ κ°μ²΄κ° μλ€λ©΄ ν΄λΉ κ°μ²΄κ°this
κ° λ©λλ€.
μμλ₯Ό ν΅ν΄ get
trap
μ μ¬μ©ν΄λ³΄κ² μ΅λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const numbers = [0, 1, 2];
const proxiedNumbers = new Proxy(numbers, {
get(target, property) {
if (property in target) {
return target[Number(property)];
} else {
return 0;
}
},
});
console.log(numbers[3]); // undefined
console.log(proxiedNumbers[1]); // 1
console.log(proxiedNumbers[3]); // 0
μ μ½λλ μ‘΄μ¬νμ§ μλ μμ μ κ·Ό μ 0μ, μ‘΄μ¬νλ μμ μ κ·Ό μ ν΄λΉ μμλ₯Ό 리ν΄νλ Proxy
κ°μ²΄μ
λλ€.
π μ£Όμν μ
Proxy
κ°μ²΄λ₯Ό μ¬μ©ν λ, μλ³Έκ°μ²΄ (νκΉκ°μ²΄)λ₯Ό μ°Έμ‘°νλ κ²μ μ§μν΄μΌν©λλ€.
π» [[Set]]
set
νΈλ©μ νλ‘νΌν°μ κ°μ μΈ λ, μ¬μ©νλ Proxy handler
μ
λλ€.
1
2
3
4
5
6
function set(
target: T,
property: string | symbol,
newValue: any,
receiver: any
): boolean;
- target: λμμ μ λ¬ν λμκ°μ²΄ (μλ³Έκ°μ²΄) μ λλ€.
- property: μ€μ ν νλ‘νΌν°μ μ΄λ¦λλ
Symbol
μ λλ€. - newValue: μ€μ ν νλ‘νΌν°μ μ κ° μ λλ€.
- receiver:
get
νΈλ©κ³Ό μ μ¬νκ² λμνλ κ°μ²΄μ λλ€.
π μ£Όμν μ
κ°μ μ°λκ²μ΄ μλ£λλ©΄
true
, κ·Έλ μ§ μμ κ²½μ°λ λ°λμfalse
λ₯Ό λ°νν΄μΌ ν©λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const numbers: number[] = [];
const proxiedNumbers = new Proxy(numbers, {
set(target, property, newValue) {
if (typeof newValue === "number") {
target[Number(property)] = newValue;
return true;
} else {
return false;
}
},
});
proxiedNumbers.push(1);
proxiedNumbers.push(2);
console.log(proxiedNumbers.length); // 2
proxiedNumbers.push("a"); // 'set' on proxy: trap returned falsish for property
μ μμλ λ°°μ΄μ μ«μμμλ§ μΆκ°ν μ μκ²λ set
νΈλ©μΌλ‘ κ°μ κ²μ¦νλ μ½λμ
λλ€.
μ«μμΌ κ²½μ° true
λ₯Ό λ°νν΄ μ μμ μΌλ‘ μΆκ° λμμμ μλ¦¬κ³ , κ·Έλ μ§ μμ κ²½μ° false
λ₯Ό λ°νν΄ μλ¬λ₯Ό λ°μ μν΅λλ€.
π» [[OwnPropertyKeys]], [[GetOwnProperty]]
κ°μ²΄ νλ‘νΌν°λ₯Ό μνν λμ μμ
μ κ°λ‘μ±κΈ° μν΄μλ ownKeys
νΈλ©μ μ¬μ©νλ©΄ λ©λλ€.
for..in
, Object.keys
, Object.values
, Object.entries
, Object.getOwnPropertyNames
, Object.getOwnPropertySymbol
μ κ°μ νλ‘νΌν° μν λ©μλλ₯Ό νΈμΆν λ λ΄λΆ λ©μλ [[OwnPropertyKeys]]
λ₯Ό νΈμΆνμ¬ νλ‘νΌν° λͺ©λ‘μ κ°μ Έμ€κ² λ©λλ€.
1
function ownKeys(target: T): ArrayLike(string | symbol)
- target: λμμ μ λ¬ν λμκ°μ²΄ (μλ³Έκ°μ²΄) μ λλ€.
π for...in
vs Object.[keys|values|entries]
vs getOwnPropertyNames
vs getOwnPropertySymbols
μμ λ©μλλ λͺ¨λ κ°μ²΄μ νλ‘νΌν°λ₯Ό μννλ λ©μλμ΄μ§λ§ μ°¨μ΄κ° μ‘΄μ¬ν©λλ€.
getOwnPropertyNames
Symbol
μ μ μΈν κ°μ²΄μ λͺ¨λ μμ±(μ΄κ±°ν μ μλ μμ± ν¬ν¨)λ€μ λ°°μ΄λ‘ λ°νν©λλ€.
1 2 3 4 5 6 7 8 const obj = {}; Object.defineProperties(obj, { one: { enumerable: true, value: 1 }, two: { enumerable: false, value: 2 }, }); console.log(Object.getOwnPropertyNames(obj)); // ['one', 'two']
getOwnPropertySymbols
κ°μ²΄μμ μ°Ύμ λͺ¨λSymbol
νλ‘νΌν°λ€μ λ°°μ΄λ‘ λ°νν©λλ€.
for...in
μμλ μ΄κ±° κ°λ₯ν νλ‘νΌν°λ€μ ν¬ν¨νμ¬ κ°μ²΄μμ λ¬Έμμ΄λ‘ ν€κ° μ§μ λ λͺ¨λ μ΄κ±° κ°λ₯ν νλ‘νΌν°μ λν΄ λ°λ³΅νλ μ°μ°μ μ λλ€.
Symbol
ν€λ 무μν©λλ€.
1 2 3 4 5 6 7 8 9 10 11 const obj = {}; Object.defineProperties(obj, { one: { enumerable: true, value: 1 }, two: { enumerable: false, value: 2 }, three: { enumerable: true, value: 3 }, }); for (const key in obj) { console.log(key); // one -> three }
Object.[keys|values|entries]
μ΄κ±°κ°λ₯ν νλ‘νΌν°λ€μ λνμ¬,Symbol
νμ΄ μλ ν€λSymbol
νμ΄ μλ κ° μ 체λ₯Ό λ°°μ΄λ‘ λ°νν©λλ€.
1 2 3 console.log(Object.keys(obj)); // ['one', 'three'] console.log(Object.values(obj)); // [1, 3] console.log(Object.entires(obj)); // [['one', 1], ['three', 3]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const monster = {
name: "monster",
eyeCount: 4,
_age: 111,
};
const proxiedMonster = new Proxy(monster, {
ownKeys(target) {
return Object.keys(target).filter((key) => !key.startsWith("_"));
},
});
for (const key in proxiedMonster) {
console.log(key); // name -> eyeCount
}
console.log(Object.getOwnPropertyNames(proxiedMonster)); // ['name', 'eyeCount']
console.log(Object.keys(proxiedMonster)); // ['name', 'eyeCount']
console.log(Object.values(proxiedMonster)); // ['monster', 4]
console.log(Object.entries(proxiedMonster)); // [['name', 'monster'], ['eyeCount', 4]]
μ μμλ ownKeys
νΈλ©μ μ¬μ©νμ¬ _
λ‘ μμνλ νλ‘νΌν°λ₯Ό μνλμμμ μ μΈνλ μ½λμ
λλ€.
λ§μ½ μλ μμμ²λΌ κ°μ²΄λ΄μ μ‘΄μ¬νμ§ μλ νλ‘νΌν°μ λͺ©λ‘μ ownKeys
νΈλ©μμ λ°ννλ €κ³ νλ©΄ μ΄λ»κ² λ κΉμ?
1
2
3
4
5
6
7
8
9
10
const obj = {};
const proxiedObj = new Proxy(obj, {
ownKeys(target) {
return ["one", "two", "three"];
},
});
console.log(Object.getOwnPropertyNames(proxiedObj)); // ['one', 'two', 'three']
console.log(Object.keys(proxiedObj)); // []
Object.getOwnPropertyNames
μ Object.keys
λ₯Ό μ¬μ©νμ λ κ²°κ³Όκ°μ΄ λ¬λΌμ§λ κ²μ λ³Ό μ μμ΅λλ€.
μ΄λ ownKeys
νΈλ©μ΄ λ°ννλ λ°°μ΄μ΄ μ΄κ±° κ°λ₯ν μμ±μ΄ μλμ¬μ Object.[keys|values|entries]
λ‘ μνκ° λΆκ°λ₯ νκΈ° λλ¬Έμ
λλ€.
Object.keys
μ κ°μ λ©μλλ νλ‘νΌν°κ° μ΄κ±° κ°λ₯νμ§μ μ¬λΆλ₯Ό νλ¨νκΈ° μν΄ [[GetOwnProperty]]
λ΄λΆ λ©μλλ₯Ό νΈμΆν΄ λͺ¨λ νλ‘νΌν°μ descriptor
λ₯Ό νμΈν©λλ€.
μ μμμ κ²½μ° ownKeys
νΈλ©μ΄ λ°ννλ νλ‘νΌν°λ€μ descriptor
κ° μ μλμ§ μμμ΅λλ€. λ°λΌμ Object.keys
μ κ°μ λ©μλλ‘ μννκΈ° μν΄μλ getOwnPropertyDescriptor
νΈλ©μ μ¬μ©ν΄ descriptor
λ₯Ό μ μν΄ μ£Όμ΄μΌ ν©λλ€.
1
2
3
4
function getOwnPropertyDescriptor(
target: any,
property: string | symbol
): PropertyDescriptor | undefined;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {};
const proxiedObj = new Proxy(obj, {
ownKeys(target) {
return ["one", "two", "three"];
},
getOwnPropertyDescriptor(target, property) {
return {
enumerable: true,
configurable: true,
};
},
});
console.log(Object.keys(proxiedObj)); // ["one", "two", "three"]
μ μμμμ getOwnPropertyDescriptor
νΈλ©μ λͺ¨λ νλ‘νΌν°λ₯Ό λμμΌλ‘ μ€νμ΄ λ©λλ€. getOwnPropertyDescriptor
νΈλ©μ property
λ₯Ό μ½μλ‘ μΆλ ₯ν΄λ³΄λ©΄ ownKeys
μμ λ°νν νλ‘νΌν°λ€μ΄ μμ°¨μ μΌλ‘ μΆλ ₯λλ κ²μ νμΈν μ μμ΅λλ€.
π» [[Delete]]
deleteProperty
νΈλ©μ νλ‘νΌν°μ κ°μ μ§μΈ λ, μ¬μ©νλ Proxy handler
μ
λλ€.
1
function deleteProperty(target: T, property: string | symbol): boolean;
π μ£Όμν μ
κ°μ μ§μ°λκ²μ΄ μλ£λλ©΄
true
, κ·Έλ μ§ μμ κ²½μ°λ λ°λμfalse
λ₯Ό λ°νν΄μΌ ν©λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const monster = {
name: "monster",
eyeCount: 4,
_age: 111,
};
const proxiedMonsters = new Proxy<Record<string | symbol, any>>(monster, {
get(target, property) {
if (typeof property === "string") {
if (property.startsWith("_")) {
throw new Error(`cannot get ${property}`);
}
}
const value = target[property];
return typeof value === "function" ? value.bind(target) : value;
},
// set μλ΅
deleteProperty(target, property) {
if (typeof property === "string") {
if (property.startsWith("_")) {
throw new Error(`cannot delete ${property}`);
}
}
delete target[property];
return true;
},
});
try {
const monsterAge = proxiedMonster._age;
} catch (error) {
console.log(error); // cannot get _age
}
try {
delete proxiedMonster._age;
} catch (error) {
console.log(error); // cannot delete _age
}
μ μμλ _
λ‘ μμνλ νλ‘νΌν°μ μ κ·Ό(get
), κ°μ μ¬μ€μ (set
), μμ (delete
)λ₯Ό λ§λ μ½λ μ
λλ€.
π value.bind(this)
μ μ½λμ
get
νΈλ©μμ λ°ννλ μ½λλ₯Ό 보면value === "function" ? value(this) : value
μ κ°μ΄ λμ΄μλκ²μ νμΈν μ μμ΅λλ€. ν΄λΉ μ½λμ μλ―Έλ νλ‘νΌν°μ ν΄λΉνλ μλ³Έ κ°μ²΄μ κ°μ΄ ν¨μμ΄λ©΄ μλ³Έ κ°μ²΄μbind
νλΌλ μλ―Έμ λλ€.
μλμ μμμμ μλ³Έ κ°μ²΄μ ν¨μμμ μκΈ° μμ μ κ°λ₯΄ν€λthis
κ° μλ€λ©΄, μ΄ κ°μProxy
κ°μ²΄μμλProxy
κ°μ²΄λ₯Ό κ°λ₯΄ν€κ² λ©λλ€.
1 2 3 4 5 6 7 8 9 10 11 const originObj = { getMe() { return this; }, }; console.log(originObj.getMe() === originObj); // true const proxiedObj = new Proxy(originObj, {}); console.log(proxiedObj.getMe() === proxiedObj); // true
delete
νΈλ©μμ 보μλmonster
κ°μ²΄μμ_age
μ λ°ννλ ν¨μκ° μλ€κ³ κ°μ ν΄λ³΄κ² μ΅λλ€. λ§μ½value.bind
λ₯Ό ν΄μ λ°ννμ§ μλλ€λ©΄this
λproxiedMonster
λ₯Ό μλ―Ένκ²λκ³ ,get
νΈλ©μ΄ λμν΄ μ€λ₯κ° λ°μν κ²μ λλ€.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const monster = { // μλ΅ getAge() { return this._age; }, }; const proxiedMonster = new Proxy<Record<string | symbol, any>>(monster, { get(target, property) { if (typeof property === "string") { if (property.startsWith("_")) { throw new Error(`cannot get ${property}`); } } return target[property]; }, }); proxiedMonster.getAge(); // cannot get _age
π» [[HasProperty]]
has
νΈλ©μ in
μ°μ°μλ₯Ό μ¬μ©ν λ, μμ
μ κ°λ‘μ±κΈ° μν΄ μ¬μ©νλ Proxy handler
μ
λλ€.
1
function has(target: T, property: string | symbol): boolean;
target
: λμμ μ λ¬ν λμκ°μ²΄ (μλ³Έκ°μ²΄) μ λλ€.property
: μ€μ ν νλ‘νΌν°μ μ΄λ¦λλSymbol
μ λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ageRange = {
min: 10,
max: 50,
};
const proxiedAgeRange = new Proxy(ageRange, {
has(target, property) {
if (typeof property === "string" && !Number.isNaN(parseInt(property))) {
return (
parseInt(property) >= target.min && parseInt(property) <= target.max
);
}
return property in target;
},
});
console.log(10 in proxiedAgeRange); // true
π» [[Call]]
[[Call]]
λ΄λΆ λ©μλλ ν¨μ νΈμΆμ ν λ νΈμΆλλ λ΄λΆ λ©μλλ‘, apply
νΈλ©μ ν΅ν΄ μμ
μ κ°λ‘μ± μ μμ΅λλ€.
1
function apply(target: T, thisArg: any, argArray: any[]): any;
target
: λμμ μ λ¬ν λμκ°μ²΄ (μλ³Έκ°μ²΄) μ λλ€.thisArg
: ν¨μ νΈμΆμ λνthis
κ°μ λλ€.argArray
: ν¨μ νΈμΆμ λν νλΌλ―Έν° λͺ©λ‘μ λλ€.
1
2
3
4
5
6
7
8
const proxiedFunction = new Proxy(function () {}, {
apply(target, thisArg, argumentsList) {
console.log(`called: ${argumentsList}`);
return argumentsList[0] + argumentsList[1] + argumentsList[2];
},
});
console.log(proxiedFunction(1, 2, 3)); // "called: 1,2,3", 6