๋ฐ˜์‘ํ˜•
๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”

 

Vue.js์—์„œ Reactive๋กœ ๋ฐ˜์‘์„ฑ์„ ์ฃผ์ž…ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ˜์†”๋กœ ์ฐ์–ด๋ณด๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ Proxy `{ ... }` ํ˜•ํƒœ๋กœ ์ถœ๋ ฅํ•œ๋‹ค. Proxy๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž…๋ ฅํ•œ ๋ฐ์ดํ„ฐ(์ƒํƒœ)๋ฅผ ํ•œ ๋ฒˆ ๊ฐ์‹ผ ๊ฒƒ์ด๋‹ค. ์ด๋ ‡๊ฒŒ Proxy๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ์˜ ์ฝ๊ธฐ / ์“ฐ๊ธฐ ๊ฐ™์€ ์ž‘์—…์„ ์ค‘๊ฐ„์— ๊ฐ€๋กœ์ฑ„์„œ ์›ํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์‚ฌ์šฉ ๋ฐฉ๋ฒ•


const proxy = new Proxy(target, handler);

 

  • target : Proxy๋กœ ๊ฐ์Œ€ ์›๋ณธ ๊ฐ์ฒด. ํ•จ์ˆ˜๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ๊ฐ์ฒด ๊ฐ€๋Šฅ
  • handler : get์ด๋‚˜ set ๊ฐ™์€ ๋™์ž‘์„ ๊ฐ€๋กœ์ฑ„๋Š” ๋ฉ”์„œ๋“œ(ํŠธ๋žฉ; trap)์ด ๋‹ด๊ธด ๊ฐ์ฒด.

 

๊ฐ์ฒด๋ฅผ proxy๋กœ ๊ฐ์‹ผ ํ›„, handler์— ์ƒ์‘ํ•˜๋Š” ํŠธ๋žฉ(๋ฉ”์„œ๋“œ)๊ฐ€ ์žˆ์œผ๋ฉด ํŠธ๋žฉ์ด ์‹คํ–‰๋œ๋‹ค. ํŠธ๋žฉ์ด ์—†์œผ๋ฉด target์—์„œ ์ž‘์—…์ด ์ˆ˜ํ–‰๋œ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ์—์„  ํŠธ๋žฉ(handler)์ด ๋น„์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— proxy์— ๋Œ€ํ•œ ๋ชจ๋“  ์ž‘์—…์ด target์œผ๋กœ ์ „๋‹ฌ๋œ๋‹ค.

const user = { name: 'johan', age: 30 };
const proxy = new Proxy(user, {});

console.log(proxy); // Proxy {name: 'johan', age: 30}
proxy.test = 30; // ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€
for (const key in proxy) console.log(key); // name, age, test

 

โญ๏ธ ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ ๋Œ€์‘ ํŠธ๋žฉ

๊ฐ์ฒด์— ์–ด๋–ค ์ž‘์—…์„ ํ•  ๋•Œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ช…์„ธ์— ์ •์˜๋œ ๋‚ด๋ถ€ ๋งค์„œ๋“œ(Internal Method)๊ฐ€ ๊ด€์—ฌํ•œ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฝ์„ ๋• [[Get]], ํ”„๋กœํผํ‹ฐ๋ฅผ ์“ธ ๋• [[Set]] ์ด๋ผ๋Š” ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๊ฐ€ ๊ด€์—ฌํ•œ๋‹ค. ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๋‹ค. Proxy์˜ ํŠธ๋žฉ์€ ์ด๋Ÿฐ ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ์˜ ํ˜ธ์ถœ์„ ๊ฐ€๋กœ์ฑ„๋ฉฐ, ๋ชจ๋“  ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ์—” ๋Œ€์‘ํ•˜๋Š” ํŠธ๋žฉ์ด ์žˆ๋‹ค.

๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ Proxy ํ•ธ๋“ค๋Ÿฌ(ํŠธ๋žฉ) ๋ฉ”์„œ๋“œ ํ•ธ๋“ค๋Ÿฌ(ํŠธ๋žฉ) ์ž‘๋™ ์‹œ์ 
[[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`

 

Proxy ๊ทœ์น™

  • ๊ฐ’ ์“ฐ๋Š” ์ž‘์—… ์„ฑ๊ณต ์‹œ [[Set]] ๋ฉ”์„œ๋“œ๋Š” ๋ฐ˜๋“œ์‹œ true ๋ฐ˜ํ™˜. ์‹คํŒจ ์‹œ false ๋ฐ˜ํ™˜
  • ๊ฐ’ ์‚ญ์ œ ์ž‘์—… ์„ฑ๊ณต ์‹œ [[Delete]] ๋ฉ”์„œ๋“œ๋Š” ๋ฐ˜๋“œ์‹œ true ๋ฐ˜ํ™˜. ์‹คํŒจ ์‹œ false ๋ฐ˜ํ™˜
  • ํ”„๋ฝ์‹œ ๊ฐ์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ [[GetPrototypeOf]] ๋ฉ”์„œ๋“œ๊ฐ€ ์ ์šฉ๋˜๋ฉด ํ”„๋ฝ์‹œ ํƒ€๊นƒ ๊ฐ์ฒด์— [[GetPrototypeOf]] ๋ฅผ ์ ์šฉํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ฐ’ ๋ฐ˜ํ™˜

 

ํŠธ๋žฉ


์กฐํšŒ — Get

๐Ÿ’ก get ๋ฉ”์„œ๋“œ(ํŠธ๋žฉ)๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์ž‘๋™ํ•œ๋‹ค.

get(target, property, receiver)

 

  • target : ์›๋ณธ ๊ฐ์ฒด. new Proxy() ์ฒซ๋ฒˆ์งธ ์ธ์ž์™€ ๋™์ผ
  • property : ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„
  • receiver : ์ตœ์ดˆ ์ž‘์—… ์š”์ฒญ์„ ๋ฐ›์€ ๊ฐ์ฒด
    ๋ณดํ†ต์€ proxy ๊ฐ์ฒด ์ž์ฒด๊ฐ€ receiver๊ฐ€ ๋˜๋ฉฐ, Proxy ๊ฐ์ฒด๋ฅผ ์ƒ์†๋ฐ›์€ ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ receiver๊ฐ€ ๋œ๋‹ค. ํƒ€๊ฒŸ ํ”„๋กœํผํ‹ฐ๊ฐ€ getter๋ผ๋ฉด getter๋ฅผ ํ˜ธ์ถœํ•œ ์‹œ์ ์˜ this๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค. ์ฃผ๋กœ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ด๋‹์—์„œ ์ตœ์ดˆ๋กœ ์ž‘์—… ์š”์ฒญ์„ ๋ฐ›์€ ๊ฐ์ฒด๋ฅผ ํ™•์ธํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

 

์•„๋ž˜๋Š” get ํŠธ๋žฉ์„ ์ด์šฉํ•ด ํ”„๋กœํผํ‹ฐ์˜ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•œ ์˜ˆ์‹œ(์กด์žฌํ•˜์ง€ ์•Š๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜).

// ์˜ˆ์‹œ 1
let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
  get(target, prop) {
    // ์—ฌ๊ธฐ์„œ ํ”„๋กœํผํ‹ฐ๋Š” 0, 1, 2 ์ธ๋ฑ์Šค
    if (prop in target) return target[prop];
    // hasOwnProperty๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค ex) *if (target.hasOwnProperty(prop)) {...}*
    // Reflect๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค(์ถ”์ฒœ) *ex) if (Reflect.has(target, prop)) {...}*
    return 0; // ๊ธฐ๋ณธ๊ฐ’
  },
});

numbers[1]; // 1
numbers[8]; // 0 (8๋ฒˆ ์ธ๋ฑ์Šค์— ํ•ด๋‹นํ•˜๋Š” ์š”์†Œ๊ฐ€ ์—†์œผ๋ฏ€๋กœ 0 ๋ฐ˜ํ™˜)
// ์˜ˆ์‹œ 2
let dictionary = { Hello: '์•ˆ๋…•ํ•˜์„ธ์š”', Bye: '์•ˆ๋…•ํžˆ๊ฐ€์„ธ์š”' };
dictionary = new Proxy(dictionary, {
  get(target, phrase) {
    // ํ”„๋กœํผํ‹ฐ ์ฝ๊ธฐ๋ฅผ ๊ฐ€๋กœ์ฑ”
    if (phrase in target) return target[phrase];
    // ๊ตฌ์ ˆ์ด ์žˆ์œผ๋ฉด ๋ฒˆ์—ญ๋ฌธ ๋ฐ˜ํ™˜
    return phrase; // ๊ตฌ์ ˆ์ด ์—†์œผ๋ฉด ๊ตฌ์ ˆ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜
  },
});

dictionary['Hello']; // '์•ˆ๋…•ํ•˜์„ธ์š”'
dictionary['Good Night']; // 'Good Night' (ํ•ด๋‹นํ•˜๋Š” ๊ตฌ์ ˆ์ด ์—†์œผ๋ฏ€๋กœ ๊ตฌ์ ˆ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜)

 

๐Ÿ’ก Target ๊ฐ์ฒด๋ฅผ Proxy๋กœ ๊ฐ์‹ผ ํ›„๋ถ€ํ„ด ๋ฐ˜๋“œ์‹œ Target ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์ฐธ์กฐํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์—†์–ด์•ผ ํ•œ๋‹ค.

 

์ฐธ๊ณ ๋กœ in ์—ฐ์‚ฐ์ž๋Š” ๋ช…์‹œํ•œ ์†์„ฑ์ด ๊ฐ์ฒด์— ์กด์žฌํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ boolean ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

const arr = [1, 2, 3, 4, 5];
0 in arr; // true (0๋ฒˆ index๊ฐ€ ์กด์žฌํ•˜๋ฏ€๋กœ)
5 in arr; // false (5๋ฒˆ index๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ)
'length' in arr; // true (๋ฐฐ์—ด์€ length ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ)
'concat' in arr; // true

const obj = { name: 'Smith', age: 30 };
'name' in obj; // true
'city' in obj; // false

 

๊ฐ์ฒด์— ํŠน์ • ํ”„๋กœํผํ‹ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” hasOwnProperty ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด no-prototype-builtins ESLint ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ํ™•์ธํ•˜๋ ค๋Š” ๊ฐ์ฒด์— hasOwnProperty์™€ ๋™์ผํ•œ ์ด๋ฆ„์˜ ํ”„๋กœํผํ‹ฐ๊ฐ€ ์žˆ์„ ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. call ๋ฉ”์„œ๋“œ๋กœ this๋ฅผ ๋ฐ”์ธ๋”ฉํ•ด์„œ ํ˜ธ์ถœํ•˜๋ฉด ESLint ๊ฒฝ๊ณ ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์–ด์ง„๋‹ค.

Object.prototype.hasOwnProperty.call(target, prop); // true | false

 

hasOwnProperty ๋Œ€์‹  Reflect ๊ฐ์ฒด๊ฐ€ ์ œ๊ณตํ•˜๋Š” has ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค(์ฐธ๊ณ ).

Reflect.has(target, prop) // true | false

 

์“ฐ๊ธฐ — Set

๐Ÿ’ก set ๋ฉ”์„œ๋“œ(ํŠธ๋žฉ)๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ์“ธ ๋•Œ(write) ์ž‘๋™ํ•œ๋‹ค.

set(target, property, value, receiver)

 

  • target : ์›๋ณธ ๊ฐ์ฒด. new Proxy() ์ฒซ๋ฒˆ์งธ ์ธ์ž์™€ ๋™์ผ
  • property : ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„
  • value : ํ”„๋กœํผํ‹ฐ ๊ฐ’
  • receiver : get ํŠธ๋žฉ๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฐ์ฒด. setter ํ”„๋กœํผํ‹ฐ์—๋งŒ ๊ด€์—ฌํ•จ

 

set์„ ์ด์šฉํ•ด ์ˆซ์ž๋งŒ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฐ์—ด์„ ๋งŒ๋“ ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ๋งŒ์•ฝ ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. value๊ฐ€ ์ˆซ์ž์ผ๋•Œ๋งŒ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์ˆซ์ž ํƒ€์ž…์ด ์•„๋‹๋• false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๋ฉด ๋œ๋‹ค.

 

๐Ÿ’ก set ํŠธ๋žฉ์„ ์‚ฌ์šฉํ•  ๋•Œ ํ•ญ์ƒ true false๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š” ์  ์žŠ์ง€ ๋ง๊ฒƒ.

let numbers = [];

numbers = new Proxy(numbers, {
  set(target, prop, value) {
    if (typeof value === 'number') {
      target[prop] = value;
      return true;
    }
    return false;
  },
});

numbers.push(1); // value๊ฐ€ ๋„˜๋ฒ„ ํƒ€์ž…์ด๋ฏ€๋กœ ์ถ”๊ฐ€ ์„ฑ๊ณต
numbers.push('2'); // Error! value๊ฐ€ ๋ฌธ์žํ˜•์ด๋ฏ€๋กœ ์ถ”๊ฐ€ ์‹คํŒจ
numbers.length; // 1

 

Proxy๋กœ ๋ฐฐ์—ด(๊ฐ์ฒด)๋ฅผ ๊ฐ์‹ธ๋„ push๋‚˜ length ๊ฐ™์€ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ณ„์† ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. push๋‚˜ unshift ๊ฐ™์€ ๋ฐฐ์—ด ๋ฉ”์„œ๋“œ๋Š” [[Set]]์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์„œ๋“œ๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š๊ณ ๋„ Proxy๊ฐ€ ๋™์ž‘์„ ๊ฐ€๋กœ์ฑˆ ํ›„ ๊ฐ’์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ.

 

๋ฐ˜๋ณต — ownKeys, getOwnPropertyDescriptor

Object.keys for...in ๊ฐ™์€ ์ˆœํ™˜ ๋ฉ”์„œ๋“œ ๋Œ€๋ถ€๋ถ„์€ ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ [[OwnPropertyKeys]](ํŠธ๋žฉ ๋ฉ”์„œ๋“œ ownKeys)๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ”„๋กœํผํ‹ฐ ๋ชฉ๋ก์„ ์–ป๋Š”๋‹ค. ์ด ์ˆœํ™˜ ๋ฉ”์„œ๋“œ์˜ ์„ธ๋ถ€ ๋™์ž‘์—” ์•„๋ž˜์™€ ๊ฐ™์€ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

intercept(ๆ‹ฆๆˆช) ํ•˜๋Š” ๋ฉ”์„œ๋“œ ์ž‘๋™ ์„ค๋ช…
`Object.getOwnPropertyNames(obj)` ์‹ฌ๋ณผํ˜•์ด ์•„๋‹Œ key๋งŒ ๋ฐ˜ํ™˜
`Object.getOwnPropertySymbols(obj)` ์‹ฌ๋ณผํ˜• key๋งŒ ๋ฐ˜ํ™˜
`Object.keys/values()` โžŠ`enumerable` ํ”Œ๋ž˜๊ทธ๊ฐ€ `true`๋ฉด์„œ ์‹ฌ๋ณผํ˜•์ด ์•„๋‹Œ key, โž‹์‹ฌ๋ณผํ˜•์ด ์•„๋‹Œ key์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ’ ์ „์ฒด ๋ฐ˜ํ™˜
`for...in` โžŠ`enumerable` ํ”Œ๋ž˜๊ทธ๊ฐ€ `true`์ด๊ณ  ์‹ฌ๋ณผํ˜•์ด ์•„๋‹Œ key, โž‹ํ”„๋กœํ† ํƒ€์ž… key ์ˆœํšŒ

 

์•„๋ž˜๋Š” ownKeys ํŠธ๋žฉ์„ ์ด์šฉํ•ด _๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ for...in ์ˆœํ™˜ ๋Œ€์ƒ์—์„œ ์ œ์™ธํ•˜๋„๋ก ํ•œ ์˜ˆ์‹œ. Object.key/values์—๋„ ๋™์ผํ•œ ๋กœ์ง์ด ์ ์šฉ๋œ๋‹ค. (_๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”„๋กœํผํ‹ฐ๋Š” ์ˆœํšŒ ๋Œ€์ƒ์—์„œ ์ œ์™ธํ•œ๋‹ค)

let user = {
  name: 'Johan',
  age: 30,
  _password: '1q2w3e4r',
};

user = new Proxy(user, {
  ownKeys(target) {
    // _๋กœ ์‹œ์ž‘ํ•˜๋Š” key๋Š” ๊ฑด๋„ˆ๋›ฐ๋„๋ก ํ•˜๋Š” ํŠธ๋žฉ
    return Object.keys(target).filter((key) => !key.startsWith('_'));
  },
});

for (const key in user) console.log(key); // name, age
Object.keys(user); // ['name', 'age']
Object.values(user); // ['John', 30]

 

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์˜๋„ํ•œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. Object.keys๋Š” ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ์ธ [[GetOwnProperty]]๋ฅผ ํ˜ธ์ถœํ•ด ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ์˜ ์„ค๋ช…์ž๋ฅผ ํ™•์ธํ•œ ํ›„ enumerable ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ๋Š” ํ”„๋กœํผํ‹ฐ๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์•„๋ž˜์—์„œ user๋Š” ํ”„๋กœํผํ‹ฐ ์„ค๋ช…์ž๋„ ์—†๊ณ  enumerable ํ”Œ๋ž˜๊ทธ๋„ ์—†์œผ๋ฏ€๋กœ ์ˆœํ™˜ ๋Œ€์ƒ์—์„œ ์ œ์™ธ๋œ ๊ฒƒ.

let user = {};

user = new Proxy(user, {
  ownKeys(target) {
    return ['a', 'b', 'c'];
  },
});

Object.keys(user); // ['a', 'b', 'c']๋ฅผ ์˜ˆ์ƒํ–ˆ์ง€๋งŒ [] ๋นˆ ๋ฐฐ์—ด๋งŒ ๋‚˜์˜ด

 

Object.keys ํ˜ธ์ถœ ์‹œ ์˜๋„ํ•œ๋Œ€๋กœ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๋ ค๋ฉด enumerable ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ์ธ [[GetOwnProperty]]๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์ด๋ฅผ ๊ฐ€๋กœ์ฑ„์„œ ์„ค๋ช…์ž enumerable: true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๋ฉด ๋œ๋‹ค. getOwnPropertyDescriptor ํŠธ๋žฉ์€ ์ด๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

let user = {};

user = new Proxy(user, {
  ownKeys(target) {
    // ํ”„๋กœํผํ‹ฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ์–ป์„ ๋•Œ 1๋ฒˆ ํ˜ธ์ถœ๋จ
    return ['a', 'b', 'c'];
  },
  getOwnPropertyDescriptor(target, prop) {
    // ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•œ๋ฒˆ์”ฉ ํ˜ธ์ถœ๋จ
    return {
      enumerable: true,
      configurable: true,
    };
  },
});

Object.keys(user); // ['a', 'b', 'c']

 

๐Ÿ’ก ๊ฐ์ฒด์— ํ”„๋กœํผํ‹ฐ๊ฐ€ ์—†์„ ๋•Œ [[GetOwnProperty]]๋ฅผ ๊ฐ€๋กœ์ฑ„์„œ ์œ„ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์  ๊ธฐ์–ต.

 

ํ”„๋กœํผํ‹ฐ ๋ณดํ˜ธ — deleteProperty

์ผ๋ฐ˜์ ์œผ๋กœ ๋‚ด๋ถ€์—์„œ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœํผํ‹ฐ / ๋ฉ”์„œ๋“œ๋Š” ์ด๋ฆ„ ์•ž์— _(์–ธ๋”์Šค์ฝ”์–ด; ๋ฐ‘์ค„)๋ฅผ ๋ถ™์ด๋Š” ์ปจ๋ฒค์…˜์ด ์žˆ๋‹ค. ์›์น™์ ์œผ๋ก  _ ๋ฐ‘์ค„์ด ๋ถ™์œผ๋ฉด ๊ฐ์ฒด ์™ธ๋ถ€์—์„  ์ด ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์–ด์•ผ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ ๋Œ€๋ถ€๋ถ„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

const user = {
  name: 'Johan',
  _password: '1q2w3e4r',
};

console.log(user._password); // 1q2w3e4r

 

Proxy๋ฅผ ์ด์šฉํ•ด _๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋‚ด๋ถ€ ํ”„๋กœํผํ‹ฐ๋ฅผ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์•„๋ž˜ ํŠธ๋žฉ์ด ํ•„์š”ํ•˜๋‹ค.

ํŠธ๋žฉ(๋ฉ”์„œ๋“œ) ์‚ฌ์šฉ ๋ชฉ์ 
get ํ”„๋กœํผํ‹ฐ ์กฐํšŒ ์‹œ ์—๋Ÿฌ ์ถœ๋ ฅ
set ํ”„๋กœํผํ‹ฐ ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ์—๋Ÿฌ ์ถœ๋ ฅ
deleteProperty ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ ์‹œ ์—๋Ÿฌ ์ถœ๋ ฅ
ownKeys `for...in` ํ˜น์€ `Object.keys` ๊ฐ™์€ ํ”„๋กœํผํ‹ฐ ์ˆœํ™˜ ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ ์‹œ `_`๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”„๋กœํผํ‹ฐ๋Š” ์ œ์™ธ

 

// ์ฝ”๋“œ ์ฐธ๊ณ  JavaScript Info
let user = {
  name: 'John',
  checkPassword(value) {
    // checkPassword(๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ)๋Š” _password๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค
    return value === this._password;
  },
  _password: '***',
};

user = new Proxy(user, {
  get(target, prop) {
    // ํ”„๋กœํผํ‹ฐ ์ฝ๊ธฐ๋ฅผ ๊ฐ€๋กœ์ฑ”
    if (prop.startsWith('_')) {
      throw new Error('์ ‘๊ทผ์ด ์ œํ•œ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.');
    }
    const value = target[prop];
    return typeof value === 'function' ? value.bind(target) : value; // (*)
  },
  set(target, prop, value) {
    // ํ”„๋กœํผํ‹ฐ ์“ฐ๊ธฐ๋ฅผ ๊ฐ€๋กœ์ฑ”
    if (prop.startsWith('_')) {
      throw new Error('์ ‘๊ทผ์ด ์ œํ•œ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.');
    } else {
      target[prop] = value;
      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('_'));
  },
});

user._password; // [Get] Error
user._password = '1q2w3e4r'; // [Set] Error
delete user._password; // [Delete] Error
for (const key in user) console.log(key); // [OwnPropertyKeys] name

 

๐Ÿ’ก bind ๋ฉ”์„œ๋“œ๋Š” ์ฒซ๋ฒˆ์งธ ์ธ์ž์— ๋ช…์‹œํ•œ this ์ปจํ…์ŠคํŠธ๋ฅผ ๋‹ด์€ ๋ฐ”์ธ๋”ฉ๋œ ํ•จ์ˆ˜๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.

 

get ํŠธ๋žฉ์—์„œ ์•„๋ž˜์ฒ˜๋Ÿผ value๊ฐ€ ํ•จ์ˆ˜์ผ ๋•Œ value.bind(target) ๋ฐ”์ธ๋”ฉ๋œ ํ•จ์ˆ˜๋ฅผ ๋ฆฌํ„ดํ•˜๊ณ  ์žˆ๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜๋Š” user.checkPassword() ๋ฉ”์„œ๋“œ๊ฐ€ _password ๊ฐ’์— ์ ‘๊ทผ ํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค.

get(target, prop) {
  // ...
  let value = target[prop];
  return (typeof value === 'function') ? value.bind(target) : value; // (*)
}

 

user.checkPassword()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด this๋Š” ํ”„๋ก์‹œ๋กœ ๊ฐ์‹ผ user๊ฐ€ ๋˜๋Š”๋ฐ(.์˜จ์  ์•ž์ด user์ด๋ฏ€๋กœ), this._password๋Š” get ํŠธ๋žฉ์„ ํ™œ์„ฑํ™”ํ•˜๋ฏ€๋กœ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด๋Ÿฐ ์ด์œ  ๋•Œ๋ฌธ์— ์›๋ณธ ๊ฐ์ฒด(target) ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฐ”์ธ๋”ฉ ์‹œํ‚จ ๊ฒƒ. ๊ทธ๋Ÿผ checkPassword()๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ this๋Š” ํŠธ๋žฉ์ด ์—†๋Š” ์›๋ณธ ๊ฐ์ฒด(target)๊ฐ€ ๋˜๊ณ , _password์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค.

 

์œ„ ๋ฐฉ๋ฒ•์€ ๋Œ€๋ถ€๋ถ„ ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ, ๋ฉ”์„œ๋“œ๊ฐ€ Proxy๋กœ ๊ฐ์‹ธ์ง€ ์•Š์€ ๊ฐ์ฒด๋ฅผ ๋„˜๊ธฐ๋Š” ์ˆœ๊ฐ„ ์—‰๋ง์ง„์ฐฝ์ด ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด์ƒ์ ์ธ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋‹ค. ๊ธฐ์กด ๊ฐ์ฒด์™€ Proxy๋กœ ๊ฐ์‹ผ ๊ฐ์ฒด๊ฐ€ ์–ด๋”” ์žˆ๋Š”์ง€ ํŒŒ์•…ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

๐Ÿ’ก ํด๋ž˜์Šค์—์„œ `#`์„ ๋ถ™์ด๋ฉด private ํ”„๋กœํผํ‹ฐ / ๋ฉ”์„œ๋“œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค(private์€ ์ƒ์† ๋ถˆ๊ฐ€).

 

๋ฒ”์œ„ ํ™•์ธ — has

๐Ÿ’ก `has` ๋ฉ”์„œ๋“œ(ํŠธ๋žฉ)๋Š” `in` ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ž‘๋™ํ•œ๋‹ค — `in` ํ˜ธ์ถœ์„ ๊ฐ€๋กœ์ฑˆ๋‹ค.

has(target, property)

 

  • target : ์›๋ณธ ๊ฐ์ฒด. new Proxy() ์ฒซ๋ฒˆ์งธ ์ธ์ž์™€ ๋™์ผ
  • property : ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„

 

์•„๋ž˜๋Š” in ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•ด ํŠน์ • ์ˆซ์ž๊ฐ€ range ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์˜ˆ์‹œ. in ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ has ํŠธ๋žฉ์ด ์ด๋ฅผ ๊ฐ€๋กœ์ฑ„๊ณ , ์ž…๋ ฅ๋ฐ›์€ prop์ด start์™€ end ํ”„๋กœํผํ‹ฐ ์‚ฌ์ด์— ์žˆ๋Š” ์ˆซ์ž์ธ์ง€ ํ™•์ธํ•œ๋‹ค.

let range = {
  start: 1,
  end: 10,
};

range = new Proxy(range, {
  has(target, prop) {
    return prop >= target.start && prop <= target.end;
  },
});

console.log(5 in range); // true (์ด๋•Œ prop์€ 5)
console.log(11 in range); // false (์ด๋•Œ prop์€ 11)

 

๋ž˜ํผ ํ•จ์ˆ˜ — apply

๐Ÿ’ก apply ๋ฉ”์„œ๋“œ(ํŠธ๋žฉ)์€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์ž‘๋™ํ•œ๋‹ค.

apply(target, thisArg, args)

 

  • target : ์›๋ณธ ๊ฐ์ฒด (JS์—์„  ํ•จ์ˆ˜ ์—ญ์‹œ ๊ฐ์ฒด๋‹ค)
  • thisArg : this์˜ ๊ฐ’
  • args : ํŒŒ๋ผ๋ฏธํ„ฐ ๋ชฉ๋ก (arguments)

 

let func = function () {
  return 'I am target';
};
func = new Proxy(func, {
  apply(target, thisArg, args) {
    return 'I am proxy';
  },
});

func(); // 'I am proxy' (์ด๋•Œ args๋Š” [])
func(2, 3); // ์ด๋•Œ args [2, 3]

 

๋ฐ์ฝ”๋ ˆ์ดํ„ฐ(Decorator)๋Š” ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฅธ ์ฝ”๋“œ๋กœ ๋ž˜ํ•‘ํ•˜๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ๋ž˜ํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ์—์„œ delay(f, ms)๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ณ , ์ด ํ•จ์ˆ˜๋Š” ms ๋ฐ€๋ฆฌ์ดˆ ํ›„์— ์‹คํ–‰๋œ๋‹ค.

function delay(f, ms) {
  // ์ผ์ • ์‹œ๊ฐ„์ด ํ›„ f๋ฅผ ํ˜ธ์ถœํ•˜๋Š” Wrapper ํ•จ์ˆ˜ ๋ฐ˜ํ™˜
  return function (...args) {
    setTimeout(() => f.apply(this, args), ms);
  };
}

let sayHi = function (user) {
  console.log(`Hello, ${user}!`);
};

sayHi = delay(sayHi, 3000); // ƒ (...args) { setTimeout(...) }
sayHi('Johan'); // (3์ดˆ ํ›„) Hello, Johan!

 

ํ•˜์ง€๋งŒ ๋ž˜ํผ ํ•จ์ˆ˜๋Š” ์ฝ๊ธฐ / ์“ฐ๊ธฐ ๋“ฑ์˜ ์—ฐ์‚ฐ์€ ์ „๋‹ฌํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๋ž˜ํผ ํ•จ์ˆ˜๋กœ ํ•œ ๋ฒˆ ๊ฐ์‹ธ๊ณ  ๋‚˜์„œ๋ถ€ํ„ด ๊ธฐ์กด ํ•จ์ˆ˜์— ์žˆ๋˜ name length ๊ฐ™์€ ํ”„๋กœํผํ‹ฐ ์ •๋ณด๋Š” ์‚ฌ๋ผ์ง„๋‹ค.

//...
console.log(sayHi.length); // 1 (ํ•จ์ˆ˜ ์ •์˜๋ถ€์— ๋ช…์‹œํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฐฏ์ˆ˜)
sayHi = delay(sayHi, 3000);
console.log(sayHi.length); // 0 (๋ž˜ํผ ํ•จ์ˆ˜ ์ •์˜๋ถ€์—” ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์Œ)

 

Proxy๋ฅผ ์ด์šฉํ•˜๋ฉด ํƒ€๊ฒŸ ๊ฐ์ฒด(์›๋ณธ ํ•จ์ˆ˜)์— ๋ชจ๋“ ๊ฑธ ์ „๋‹ฌํ•ด์ฃผ๋ฏ€๋กœ ๋” ๊ฐ•๋ ฅํ•˜๋‹ค. ์›๋ณธ ํ•จ์ˆ˜์— ์žˆ๋˜ name length ๊ฐ™์€ ํ”„๋กœํผํ‹ฐ๋„ ์ œ๋Œ€๋กœ ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

function delay(f, ms) {
  return new Proxy(f, {
    apply(target, thisArg, args) {
      setTimeout(() => target.apply(thisArg, args), ms);
    },
  });
}

// let sayHi = function() {...}  ์ƒ๋žต

sayHi = delay(sayHi, 3000);
console.log(sayHi.length); // 1 (ํ”„๋ฝ์‹œ์— ์ˆ˜ํ–‰๋˜๋Š” ๋ชจ๋“  ์—ฐ์‚ฐ์ด ์›๋ณธ ํ•จ์ˆ˜์— ์ „๋‹ฌ๋œ๋‹ค)
sayHi('Johan'); // (3์ดˆ ํ›„) Hello, Johan!

 

Observable ๋งŒ๋“ค๊ธฐ ์˜ˆ์ œ


๐Ÿ’ก Symbol์€ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€๋Šฅํ•œ ์›์‹œ ํƒ€์ž…์ด๋‹ค. ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ๊ณ ์œ ํ•œ Symbol ๊ฐ’์„ ์ƒ์„ฑํ•œ๋‹ค. Symbol() ์ธ์ž์— ๋„˜๊ธด ๋ฌธ์ž์—ด์€ Symbol ์ƒ์„ฑ์— ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ๋„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค(๋””๋ฒ„๊น… ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉ).

 

Proxy๋ฅผ ์ด์šฉํ•ด ์ „๋‹ฌ๋ฐ›์€ ๊ฐ์ฒด๋ฅผ ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•˜๋„๋ก(observable) ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ๋Š” ๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค observe ๋ฉ”์„œ๋“œ์— ๋„˜๊ธด ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•ด์„œ ํ”„๋กœํผํ‹ฐ key value(๋ณ€๊ฒฝํ›„)๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค.

 

  1. user ๊ฐ์ฒด์— observe ํ•จ์ˆ˜ ๋“ฑ๋ก → obj[handlers] ๋ฐฐ์—ด์— ํ•จ์ˆ˜ ์ €์žฅ
  2. user.name ๊ฐ’ ์“ฐ๊ธฐ ์‹œ๋„
  3. set ํŠธ๋žฉ ์‹คํ–‰ ํ›„ name ํ”„๋กœํผํ‹ฐ ๊ฐ’ ๋ณ€๊ฒฝ
  4. obj[handlers] ๋ฐฐ์—ด์— ์ €์žฅํ•œ ์ฝœ๋ฐฑ ์‹คํ–‰

 

// ์ฝ”๋“œ ์ฐธ๊ณ  JavaScript Info
const handlers = Symbol('handlers');

function makeObservable(obj) {
  obj[handlers] = []; // observe ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ›์€ handler ํ•จ์ˆ˜๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด
  obj.observe = (handler) => {
    // ์ „๋‹ฌ ๋ฐ›์€ handler ์ธ์ž ํƒ€์ž…์ด ํ•จ์ˆ˜์ผ๋•Œ๋งŒ obj[handlers] ๋ฐฐ์—ด์— ์ €์žฅ
    if (typeof handler === 'function') obj[handlers].push(handler);
  };

  return new Proxy(obj, {
    // ํ”„๋กœํผํ‹ฐ ๊ฐ’ ์ฝ๊ธฐ(get ํŠธ๋žฉ)๋„ ์•„๋ž˜(set ํŠธ๋žฉ)์™€ ๋น„์Šทํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค
    set(target, prop, value, receiver) {
      const success = Reflect.set(target, prop, value, receiver); // ํƒ€๊ฒŸ ๊ฐ์ฒด์— ๋™์ž‘ ์ „๋‹ฌ
      if (success) target[handlers].forEach((handler) => handler(prop, value));
      return success; // ํ”„๋กœํผํ‹ฐ ์“ฐ๊ธฐ ์„ฑ๊ณต์‹œ true, ์‹คํŒจ์‹œ false
    },
  });
}

const user = makeObservable({});

user.observe((key, value) => console.log(`SET ${key} = ${value}`));
user.name = 'Johan'; // SET name = Johan
user.name = 'Smith'; // SET name = Smith

 

๐Ÿ’ก ์˜ต์„œ๋ฒ„ ํŒจํ„ด๊ณผ ๊ด€๋ จํ•œ ๋” ์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ ๋‹ค๋ฅธ ์˜ˆ์ œ๋Š” ๋งํฌ ์ฐธ๊ณ .

 

Reflect


๐Ÿ’ก ๋ฉ”ํƒ€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์ค‘์— ์ž์‹  ํ˜น์€ ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ์„ ์กฐ์ž‘ํ•˜๋Š” ๊ธฐ๋ฒ•์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค. ์ž์‹ ์˜ ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ , ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜, ์ƒˆ๋กœ์šด ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๋™์ž‘์„ ํฌํ•จํ•œ๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„  ๊ฐ์ฒด์˜ ๋™์ž‘์„ ๊ฐ€๋กœ์ฑ„์„œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” Proxy์™€, ๊ฐ์ฒด์˜ ์†์„ฑ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฝ๊ณ  ์“ฐ๋Š” Reflect ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ ๋ฉ”ํƒ€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€์›ํ•œ๋‹ค.

 

Reflect๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋ฉ”ํƒ€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€์›ํ•˜๋Š” ๋‚ด์žฅ ๊ฐ์ฒด๋‹ค. Reflect ๋ฉ”์„œ๋“œ๋Š” Proxy์˜ ๊ฐ ํŠธ๋žฉ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์—(ํŠธ๋žฉ ๋ฐ˜ํ™˜ ์š”๊ตฌ์‚ฌํ•ญ ์ถฉ์กฑ), Proxy ํŠธ๋žฉ ๋‚ด์—์„œ ๊ธฐ๋ณธ ๋™์ž‘์„ ๋” ์†์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

Reflect.get(target, propertyKey[, receiver]) : Return target[property]
Reflect.set(target, propertyKey, value[, receiver]) : Return boolean
Reflect.has(target, propertyKey) : Return boolean
Reflect.deleteProperty(target, propertyKey) : Return boolean
Reflect.ownKeys(target) : Return array of property keys
…(๋” ๋งŽ์€ ์ •์  ๋ฉ”์„œ๋“œ๋Š” MDN ์ฐธ๊ณ )

 

์˜ˆ๋ฅผ๋“ค์–ด Set ํŠธ๋žฉ์—์„œ target[prop] = value ์™€ ๊ฐ™์€ ์“ฐ๊ธฐ ์ž‘์—… ํ›„ ์ผ์ผ์ด true ํ˜น์€ false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹ , Reflect.set(target, prop, value) ํ•œ ์ค„๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

const userProxy = new Proxy(
  { name: 'John', age: 30 },
  {
    set(target, prop, value) {
      return Reflect.set(...arguments); // Reflect.set(target, prop, value)
      // target[prop] = value;
      // return true;
    },
  },
);

 

Reflect.get(...) ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๋Š” ์„ ํƒ์ ์œผ๋กœ receiver ์ธ์ž๋ฅผ ๋ฐ›๋Š”๋ฐ, ์ด๋ฅผ ํ†ตํ•ด this ๋ฐ”์ธ๋”ฉ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ, ๊ฐ์ฒด์˜ ํ”„๋กœํ† ํƒ€์ž…์„ ๋ณ€๊ฒฝํ•œ ํ›„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ์ƒํ™ฉ์—์„œ ์ตœ์ดˆ ์ ‘๊ทผ ์š”์ฒญ์„ ๋ฐ›์€ ๊ฐ์ฒด์˜ this ๋ฐ”์ธ๋”ฉ์„ ์œ ์ง€ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค.

 

Receiver ๊ฐœ๋…

๐Ÿ’ก `target[property]`๊ฐ€ getter/setter(์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ)์ด๋ฉด Receiver๊ฐ€ this ์ปจํ…์ŠคํŠธ๋กœ ๋™์ž‘ํ•œ๋‹ค.

 

JS์—์„  ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ด๋‹์ด ์ด๋ค„์ง€๋”๋ผ๋„ ์ตœ์ดˆ ์ ‘๊ทผ ์š”์ฒญ์„ ๋ฐ›์€ ๊ฐ์ฒด๋ฅผ ์œ ์ง€ํ•œ๋‹ค. ์ด ๊ฐ์ฒด๋ฅผ Receiver๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. target[property]๊ฐ€ getter/setter๋ผ๋ฉด Receiver๊ฐ€ this ์ปจํ…์ŠคํŠธ๋กœ ๋™์ž‘ํ•œ๋‹ค.

 

์•„๋ž˜ ์˜ˆ์ œ์—์„œ child.age ํ”„๋กœํผํ‹ฐ ์ฝ๊ธฐ๋ฅผ ์‹œ๋„ํ•˜๋ฉด [[Get]] ๋‚ด๋ถ€ ์Šฌ๋กฏ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ด๋•Œ Receiver๋Š” child ๊ฐ์ฒด๊ฐ€ ๋œ๋‹ค. — child.[[Get]](P, Receiver)

 

child ๊ฐ์ฒด์—” age๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ด๋‹์„ ํ†ตํ•ด parent ๊ฐ์ฒด์˜ age getter*๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์ด๋•Œ Receiver(child)๊ฐ€ this๋กœ ์‚ฌ์šฉ๋ผ์„œ this._birthYear๋Š” 1990์ด ๋œ๋‹ค. *setter ์—ญ์‹œ ๋น„์Šทํ•œ ๊ณผ์ •์„ ๊ฑฐ์ณ Receiver(child)๊ฐ€ this๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 

// ์ฝ”๋“œ ์ฐธ๊ณ  Toast UI
const child = {
  _birthYear: 1990,
};

const parent = {
  _birthYear: 1984,
  get age() {
    return new Date().getFullYear() - this._birthYear;
  },
  set birthYear(year) {
    this._birthYear = year;
  },
};

Object.setPrototypeOf(child, parent); // child.__proto__ = parent;
console.log(child.age); // 32 (2022 - 1990)

 

Reflect ํ™œ์šฉ

this ๋ฐ”์ธ๋”ฉ

Reflect์˜ receiver ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” getter/setter(์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ)์—์„œ this๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 

Reflect ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด receiver ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ†ตํ•ด this ๋ฐ”์ธ๋”ฉ์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. target๊ณผ receiver๊ฐ€ ๋‹ค๋ฅด๊ณ , target[property]๊ฐ€ getter/setter์ด๋ฉด receiver ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์“ด๋‹ค. ์ฆ‰, target์˜ getter/setter์—์„œ this๋Š” receiver๊ฐ€ ๋œ๋‹ค.

const target = {
  one: 10,
  two: 20,
  get sum() {
    return this.one + this.two;
  },
};

const receiver = {
  one: 30,
  two: 40,
  get sum() {
    return this.one + this.two + 999;
  },
};

// target์˜ ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผํ•  ๋•Œ (target.one)
// target.one์€ ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ์ด๋ฏ€๋กœ receiver ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์•„๋ฌด ์—ญํ• ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค
Reflect.get(target, 'one', target); // 10
Reflect.get(target, 'one', receiver); // 10

// target์˜ ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ(getter/setter)์— ์ ‘๊ทผํ•  ๋•Œ (target.sum)
Reflect.get(target, 'sum', target); // 30 (sum getters this: target)
Reflect.get(target, 'sum', receiver); // 70 (sum getters this: receiver)

 

ํ”„๋กœํ† ํƒ€์ž… ์‚ฌ์ด๋“œ ์ดํŒฉํŠธ ํ•ด๊ฒฐ

์•„๋ž˜ ์˜ˆ์ œ์—์„œ admin.name ๊ฐ’์„ ์กฐํšŒํ•˜๋ฉด Admin์ด ์•„๋‹Œ Guest๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

admin ๊ฐ์ฒด์—” name ํ”„๋กœํผํ‹ฐ๊ฐ€ ์—†์œผ๋ฏ€๋กœ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ด๋‹์„ ํ†ตํ•ด(via __proto__) userProxy ๊ฐ์ฒด์—์„œ name์„ ์ฐพ๋Š”๋‹ค. ๊ทธ๋Ÿผ get ํŠธ๋žฉ์ด ํŠธ๋ฆฌ๊ฑฐ ๋˜๊ณ  target[prop] ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, target[prop]์ด getter๋ผ๋ฉด target ์ปจํ…์ŠคํŠธ์—์„œ ์‹คํ–‰๋œ๋‹ค. ๋•Œ๋ฌธ์— name getter ์•ˆ์—์„œ this๋Š” user(target)๊ฐ€ ๋œ๋‹ค.

// ํ”„๋กœํ† ํƒ€์ž… ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ์˜ˆ์‹œ via JavaScript Info
const user = {
  _name: 'Guest',
  get name() {
    return this._name;
  },
};

const userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    return target[prop]; // target === user
  },
});

const admin = {
  __proto__: userProxy,
  _name: 'Admin',
};

console.log(admin.name); // Guest

 

์œ„ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ Reflect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด getter์— ์˜ฌ๋ฐ”๋ฅธ ์ปจํ…์ŠคํŠธ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. receiver๋Š” ์ตœ์ดˆ ์ ‘๊ทผ ์š”์ฒญ์„ ๋ฐ›์€ ๊ฐ์ฒด(admin)๋ฅผ ์œ ์ง€ํ•˜๋ฏ€๋กœ ์ด๋ฅผ Reflect.get์— ๋„˜๊ธฐ๋ฉด getter๋Š” admin ์ปจํ…์ŠคํŠธ์—์„œ ์‹คํ–‰๋œ๋‹ค. ์ฆ‰, name getter์—์„œ this๋Š” admin์ด ๋œ๋‹ค.

const userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    // return target[prop];
    return Reflect.get(target, prop, receiver);
  },
});

 

์ค‘์ฒฉ ํ”„๋ก์‹œ


์•„๋ž˜์ฒ˜๋Ÿผ ์ค‘์ฒฉ ๊ฐ์ฒด ํ˜•์‹์œผ๋กœ ํ• ๋‹น์„ ์‹œ๋„ํ•˜๋ฉด 1 ๋Ž์Šค์— ์žˆ๋Š” wrapper ๊ฐ์ฒด๋Š” Proxy๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ๋˜์ง€๋งŒ, ๊ทธ ๋‚ด๋ถ€์— ์žˆ๋Š” ๊ฐ์ฒด(์•„๋ž˜ ์˜ˆ์‹œ์—์„œ a ์†์„ฑ)๋Š” ๋”ฐ๋กœ Proxy๋กœ ๊ด€๋ฆฌ๋˜์ง€ ์•Š๋Š”๋‹ค.

const origin = {};

const handler = {
  get(target, prop, receiver) {
    console.log('Get ํŠธ๋žฉ ์‹คํ–‰');
    return Reflect.get(...arguments);
  },
  set(target, prop, value) {
    console.log('Set ํŠธ๋žฉ ์‹คํ–‰');
    return Reflect.set(...arguments);
  },
};

const wrapper = new Proxy(origin, handler);
wrapper.a = {};
wrapper.a.b = 20;

console.log(wrapper);
// Set ํŠธ๋žฉ ์‹คํ–‰
// Get ํŠธ๋žฉ ์‹คํ–‰
// Proxy(Object) {a: {b: 20}} -> wrapper๋Š” Proxy ๊ฐ์ฒด, wrapper.a๋Š” ์ผ๋ฐ˜ ๊ฐ์ฒด

 

  1. wrapper.a = {} : Set ํŠธ๋žฉ์ด ์‹คํ–‰๋ผ์„œ a ์†์„ฑ์— {} ๋นˆ ๊ฐ์ฒด๋ฅผ ํ• ๋‹นํ•œ๋‹ค.
  2. wrapper.a.b = 20 :
    1. ๋จผ์ € wrapper.a ์†์„ฑ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด Get ํŠธ๋žฉ์ด ์‹คํ–‰๋˜๊ณ  {} ๋นˆ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    2. ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด์˜ b ์†์„ฑ์„ 20์œผ๋กœ ์ง์ ‘ ํ• ๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ์— Set ํŠธ๋žฉ์€ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.

 

๋งŒ์•ฝ ๋‚ด๋ถ€ ๊ฐ์ฒด๋„ ๋ชจ๋‘ Proxy๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด Get ํŠธ๋žฉ์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์ด ๊ฐ์ฒด ํƒ€์ž…์ผ ๋• ํ•ด๋‹น ๊ฐ์ฒด์—๋„ ๋™์ ์œผ๋กœ Proxy๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค. ์กฐํšŒํ•˜๋Š” ๊ฐ’์ด ๊ฐ์ฒด๋ผ๋ฉด ํ•ญ์ƒ ์ƒˆ๋กœ์šด Proxy๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์ด๋ฏธ ์ƒ์„ฑํ•œ Proxy๋Š” ์บ์‹œ๋กœ ๊ด€๋ฆฌํ•ด์„œ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋กœ์ง๋„ ํ•„์š”ํ•˜๋‹ค.

const origin = {};

// WeakMap์€ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ณณ์ด ์—†๋‹ค๋ฉด ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๋Œ€์ƒ์ด ๋ผ์„œ ๋ฉ”๋ชจ๋ฆฌ์™€ WeakMap์—์„œ ์‚ญ์ œ๋œ๋‹ค
const proxyCache = new WeakMap();

const handler = {
  get(target, prop, receiver) {
    console.log('Get ํŠธ๋žฉ ์‹คํ–‰');
    const value = Reflect.get(...arguments);

    if (typeof value === 'object' && value !== null) {
      if (proxyCache.has(value)) {
        return proxyCache.get(value); // ์ €์žฅ๋œ Proxy ์บ์‹œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฐ˜ํ™˜
      } else {
        const newProxy = new Proxy(value, handler); // ์ €์žฅ๋œ Proxy ์บ์‹œ๊ฐ€ ์—†์–ด์„œ ์ƒˆ๋กœ์šด Proxy ๊ฐ์ฒด ์ƒ์„ฑ
        proxyCache.set(value, newProxy); // ์ƒˆ๋กœ ์ƒ์„ฑํ•œ Proxy ๊ฐ์ฒด๋ฅผ ์บ์‹œ์— ์ €์žฅ
        return newProxy;
      }
    }

    return value;
  },
  set(target, prop, value) {
    console.log('Set ํŠธ๋žฉ ์‹คํ–‰');
    return Reflect.set(...arguments);
  },
};

const wrapper = new Proxy(origin, handler);
wrapper.a = {};
wrapper.a.b = 20;

console.log(wrapper);
// Set ํŠธ๋žฉ ์‹คํ–‰
// Get ํŠธ๋žฉ ์‹คํ–‰
// Set ํŠธ๋žฉ ์‹คํ–‰
// Proxy(Object) {a: {b: 20}} -> wrapper, wrapper.a ๋ชจ๋‘ Proxy ๊ฐ์ฒด

 

  1. wrapper.a : Set ํŠธ๋žฉ์ด ์‹คํ–‰๋ผ์„œ a ์†์„ฑ์— {} ๋นˆ ๊ฐ์ฒด๋ฅผ ํ• ๋‹นํ•œ๋‹ค.
  2. wrapper.a.b = 20 :
    1. Get ํŠธ๋žฉ์ด ์‹คํ–‰๋ผ์„œ a ์†์„ฑ ๊ฐ’์ด ๊ฐ์ฒด์ธ์ง€ ํ™•์ธํ•œ๋‹ค. a ์†์„ฑ ๊ฐ’์€ ๋นˆ ๊ฐ์ฒด์ด๊ณ  proxyCache์— ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ๋กœ์šด Proxy ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  proxyCache์— ์ €์žฅํ•œ ํ›„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    2. ๋ฐ˜ํ™˜๋œ Proxy ๊ฐ์ฒด์˜ b ์†์„ฑ์— ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์— Set ํŠธ๋žฉ์ด ์‹คํ–‰๋˜๊ณ , b ์†์„ฑ์— 20์„ ํ• ๋‹นํ•œ๋‹ค.

 

๋ ˆํผ๋Ÿฐ์Šค


๋ฐ˜์‘ํ˜•