ν™ˆ Proxy
포슀트
μ·¨μ†Œ

Proxy

πŸ’» Proxyλž€?

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 MethodHandler Methodλ™μž‘ μ‹œμ 
[[GetPrototypeOf]]getPrototypeOfObject.getPrototypeOf 호좜 μ‹œ
[[SetPrototypeOf]]setPrototypeOfObject.setPrototypeOf 호좜 μ‹œ
[[isExtensible]]isExtensibleObject.isExtensible 호좜 μ‹œ
[[preventExtension]]preventExtensionsObject.preventExtensions 호좜 μ‹œ
[[GetOwnProperty]]getOwnPropertyDescriptorObject.getOwnPropertyDescriptor
for..in
Object.[keys | values | entries] 호좜 μ‹œ
[[DefineOwnProperty]]definePropertyObject.defineProperty 호좜 μ‹œ
[[HasProperty]]hasin μ—°μ‚°μž μ‚¬μš©μ‹œ
[[Get]]getν”„λ‘œνΌν‹°λ₯Ό 읽을 λ•Œ
[[Set]]setν”„λ‘œνΌν‹°λ₯Ό μ“Έ λ•Œ
[[Delete]]deletePropertydelete μ—°μ‚°μž μ‚¬μš© μ‹œ
[[OwnPropertyKeys]]ownKeysObject.getOwnPropertyNames,
Object.getOwnPropertySymbols,
for..in
Object.[keys | values | entries] 호좜 μ‹œ
[[Call]]applyν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•  λ•Œ
[[Constructor]]constructornew μ—°μ‚°μžκ°€ μ‚¬μš© μ‹œ

πŸ’» [[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
이 κΈ°μ‚¬λŠ” μ €μž‘κΆŒμžμ˜ CC BY 4.0 λΌμ΄μ„ΌμŠ€λ₯Ό λ”°λ¦…λ‹ˆλ‹€.

μ›Ήλ·°μ—μ„œ λͺ¨λ°”일 κΈ°κΈ° νŒλ³„ν•˜κΈ°

npm νŒ¨ν‚€μ§€ 버전