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

 

ํ”„๋กœํผํ‹ฐ ํ”Œ๋ž˜๊ทธ


๊ฐ์ฒด๋Š” ๊ฐ’(value) ์™ธ์—๋„ ํ”Œ๋ž˜๊ทธ(flag)๋ผ๋Š” ํŠน๋ณ„์ƒ ์†์„ฑ์ด ์žˆ๋‹ค. ํ”Œ๋ž˜๊ทธ๋Š” ์•„๋ž˜ 3๊ฐ€์ง€ ์ข…๋ฅ˜๊ฐ€ ์žˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•˜๋ฉด(๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด ํ˜น์€ Object ์ƒ์„ฑ์ž ํ•จ์ˆ˜ ์‚ฌ์šฉ) ํ”„๋กœํผํ‹ฐ์˜ ํ”Œ๋ž˜๊ทธ๋Š” `true`๋ฅผ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๊ฐ€์ง„๋‹ค.

  ํ”„๋กœํผํ‹ฐ ๊ฐ’ ์ˆ˜์ • ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ ๋ฐ˜๋ณต๋ฌธ ๋‚˜์—ด ํ”Œ๋ž˜๊ทธ ์ˆ˜์ •
`writable: false` โŒ โœ… โœ… โœ…
`enumerable: false` โœ… โœ… โŒ โœ…
`configurable: false` โœ… โŒ โœ… โŒ

 

  1. writable (์ˆ˜์ •)
    • `true` : ํ”„๋กœํผํ‹ฐ ๊ฐ’ ์ˆ˜์ • ๊ฐ€๋Šฅ
    • `false` : ํ”„๋กœํผํ‹ฐ ๊ฐ’ ์ˆ˜์ • ๋ถˆ๊ฐ€ (ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ๋Š” ๊ฐ€๋Šฅ)
  2. enumerable (์—ด๊ฑฐ)
    • `true` : ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ๋‚˜์—ด ๊ฐ€๋Šฅ
    • `false` : ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ๋‚˜์—ด ๋ถˆ๊ฐ€
  3. configurable
    • `true` : ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ / ํ”Œ๋ž˜๊ทธ ์ˆ˜์ • ๊ฐ€๋Šฅ
    • `false` : ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ(ํ”„๋กœํผํ‹ฐ ๊ฐ’ ์ˆ˜์ •์€ ๊ฐ€๋Šฅ) / ํ”Œ๋ž˜๊ทธ ์ˆ˜์ • ๋ถˆ๊ฐ€

 

ํ”„๋กœํผํ‹ฐ ์„ค๋ช…์ž ๊ฐ์ฒด — Object.getOwnPropertyDescriptor

ํ”„๋กœํผํ‹ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋Š” Object.getOwnPropertyDescriptor ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ”„๋กœํผํ‹ฐ ์„ค๋ช…์ž(descriptor)๋ผ๋Š” ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. ์ด ์„ค๋ช…์ž ๊ฐ์ฒด์— ํ”Œ๋ž˜๊ทธ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ๋‹ค.

Object.getOwnPropertyDescriptor(obj, property)

 

const user = { name: 'john', age: 30 };
const descriptor = Object.getOwnPropertyDescriptor(user, 'name');

console.log(descriptor);
// {value: 'john', writable: true, enumerable: true, configurable: true}

 

ํ”„๋กœํผํ‹ฐ / ํ”Œ๋ž˜๊ทธ ์ •์˜ ๋ฐ ๋ณ€๊ฒฝ — Object.defineProperty

๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด, ๊ฐ์ฒด ๋ณต์‚ฌ(Object.assign, ์ „๊ฐœ์—ฐ์‚ฐ์ž)๋“ฑ์œผ๋กœ ๋งŒ๋“  ๊ฐ์ฒด์˜ ๋ชจ๋“  ํ”Œ๋ž˜๊ทธ ๊ธฐ๋ณธ๊ฐ’์€ `true`
`Object.defineProperty`๋กœ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๋ช…์‹œํ•˜์ง€ ์•Š์€ ๋ชจ๋“  ํ”Œ๋ž˜๊ทธ ๊ธฐ๋ณธ๊ฐ’์€ `false`

 

Object.defineProperty ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด์˜ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. ์ธ์ž๋กœ ๋„˜๊ฒจ๋ฐ›์€ ์ •๋ณด๋ฅผ ์ด์šฉํ•ด ์ƒˆ๋กœ์šด ํ”„๋กœํผํ‹ฐ๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค. ์ด๋•Œ ํ”Œ๋ž˜๊ทธ ์ •๋ณด๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด ํ”Œ๋ž˜๊ทธ ๊ฐ’์€ ์ž๋™์œผ๋กœ `false`๊ฐ€ ๋œ๋‹ค.

Object.defineProperty(obj, property, descriptors)

 

const user = {};
Object.defineProperty(user, 'name', {
  value: 'johan',
});

console.log(user); // {name: 'johan'}
console.log(Object.getOwnPropertyDescriptor(user, 'name'));
// {value: 'johan', writable: false, enumerable: false, configurable: false}

 

writable ํ”Œ๋ž˜๊ทธ

writable ํ”Œ๋ž˜๊ทธ๋ฅผ ์ด์šฉํ•ด ๊ฐ์ฒด์˜ ํŠน์ • ์†์„ฑ์˜ ๊ฐ’์„ ์“ฐ์ง€ ๋ชปํ•˜๋„๋ก(non-writable) ํ•  ์ˆ˜ ์žˆ๋‹ค. ์—๋Ÿฌ๋Š” ์—„๊ฒฉ ๋ชจ๋“œ("use strict")์—์„œ๋งŒ ๋ฐœ์ƒํ•œ๋‹ค.

const user = { name: 'john' };

Object.defineProperty(user, 'name', {
  writable: false,
});

user.name = 'smith'; // Error: Cannot assign to read only property 'name'
console.log(user); // {name: 'john'}

 

์•„๋ž˜๋Š” defineProperty ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์ง์ ‘ ํ• ๋‹นํ•ด์ค€ ์˜ˆ์‹œ. ์œ„ ์˜ˆ์ œ์™€ ๋™์ผํ•˜๋‹ค.

const user = Object.defineProperty({}, 'name', {
  value: 'john',
  enumerable: true,
  configurable: true,
});

console.log(user.name); // john
user.name = 'smith'; // Error

 

enumerable ํ”Œ๋ž˜๊ทธ

user ๊ฐ์ฒด์— toString์ด๋ผ๋Š” ์ปค์Šคํ…€ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. for in๋ฌธ์œผ๋กœ ๋Œ๋ ค๋ณด๋ฉด key ์ด๋ฆ„์ด ์ž˜ ์ถœ๋ ฅ๋œ๋‹ค. ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด๋กœ ์ƒ์„ฑํ•œ ๊ฐ์ฒด์˜ ๋ชจ๋“  ํ”Œ๋ž˜๊ทธ๋Š” true๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด๋ฏ€๋กœ enumerable ํ”Œ๋ž˜๊ทธ๋„ true์ด๋‹ค.

const user = {
  name: 'john',
  toString() {
    return this.name;
  },
};

Object.getOwnPropertyDescriptors(user);
// name: {value: 'john', writable: true, enumerable: true, configurable: true}
// toString: {writable: true, enumerable: true, configurable: true, value: ƒ}

for (const key in user) console.log(key); // name, toString
Object.keys(user); // ['name', 'toString']

 

enumerable ํ”Œ๋ž˜๊ทธ ๊ฐ’์„ false๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด for in๋ฌธ์ด๋‚˜ Object.keys() ๊ฐ™์€ ์—ด๊ฑฐ(๋ฐ˜๋ณต)๊ฐ€ ์ด๋ค„์ง€๋Š” ๊ณณ์— ์‚ฌ์šฉํ•ด๋„ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค. ์•„๋ž˜์—์„  toString ํ”„๋กœํผํ‹ฐ(key)์˜ enumerable ํ”Œ๋ž˜๊ทธ ๊ฐ’์„ false๋กœ ์„ค์ •ํ–ˆ์œผ๋ฏ€๋กœ for in๋ฌธ ๋ฐ Object.keys์—์„  ์ถœ๋ ฅ๋˜์ง€ ์•Š๊ณ  ์žˆ๋‹ค.

const user = {
  name: 'john',
  toString() {
    return this.name;
  },
};

Object.defineProperty(user, 'toString', {
  enumerable: false,
});

for (const key in user) console.log(key); // name
Object.keys(user); // ['name']

 

configurable ํ”Œ๋ž˜๊ทธ

๊ตฌ์„ฑ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•˜๋Š” configurable ํ”Œ๋ž˜๊ทธ๋Š” ์ผ๋ถ€ ๋‚ด์žฅ ๊ฐ์ฒด / ํ”„๋กœํผํ‹ฐ์— false๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค. ์–ด๋–ค ํ”„๋กœํผํ‹ฐ์—์„œ configurable ํ”Œ๋ž˜๊ทธ๊ฐ€ false๋ผ๋ฉด ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๋Š” ์‚ญ์ œํ•  ์ˆ˜ ์—†๋‹ค. Math ๊ฐ์ฒด์˜ PI ํ”„๋กœํผํ‹ฐ๊ฐ€ ๊ทธ ์˜ˆ๋‹ค.

console.log(Object.getOwnPropertyDescriptor(Math, 'PI'));
// {value: 3.141592653589793, writable: false, enumerable: false, configurable: false}
// Math.PI๋Š” ์“ฐ๊ธฐ, ์—ด๊ฑฐ, ๊ตฌ์„ฑ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.
// ๋”ฐ๋ผ์„œ Math.PI = 88 ์ฒ˜๋Ÿผ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ๋ฎ์–ด์“ฐ๋Š” ๊ฒƒ๋„ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

 

configurable: false์˜ ์ œ์•ฝ์‚ฌํ•ญ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ํŠน์ • ํ”„๋กœํผํ‹ฐ์˜ configurable ํ”Œ๋ž˜๊ทธ๋ฅผ false๋กœ ์„ค์ •ํ•˜๋ฉด ๋”์ด์ƒ true๋กœ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค. ๐Ÿ’ก configurable: false์ด๋ฉด ํ”Œ๋ž˜๊ทธ ์ˆ˜์ • / ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ๋Š” ํ•  ์ˆ˜ ์—†์ง€๋งŒ, ํ”„๋กœํผํ‹ฐ ๊ฐ’์€ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค(writable ํ”Œ๋ž˜๊ทธ๊ฐ€ true์ผ๋•Œ).

 

  1. configurable ํ”Œ๋ž˜๊ทธ ์ˆ˜์ • ๋ถˆ๊ฐ€
  2. enumerable ํ”Œ๋ž˜๊ทธ ์ˆ˜์ • ๋ถˆ๊ฐ€
  3. writable: false ๊ฐ’์„ true ๋กœ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€(truefalse ๋ณ€๊ฒฝ์€ ๊ฐ€๋Šฅ)
  4. ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ get, set ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€(์ƒˆ๋กœ ๋งŒ๋“œ๋Š”๊ฑด ๊ฐ€๋Šฅ)

 

const user = { name: 'john', age: 30 };

delete user.age; // true (age ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ๋จ)
Object.defineProperty(user, 'name', { configurable: false });
delete user.name; // false (์‚ญ์ œ ๋ถˆ๊ฐ€)
Object.defineProperty(user, 'name', { writable: false }); // true → false๋กœ ๋ฐ”๊พธ๋Š”๊ฑด ๊ฐ€๋Šฅ
Object.defineProperty(user, 'name', { writable: true }); // Error, Cannot redefine property

 

์—ฌ๋Ÿฌ ํ”„๋กœํผํ‹ฐ ์ •์˜ — Object.defineProperties

Object.defineProperties ๋ฉ”์„œ๋“œ๋กœ ์—ฌ๋Ÿฌ ํ”„๋กœํผํ‹ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค

Object.defineProperties(obj, descriptors)

 

const user = Object.defineProperties(
  {},
  {
    name: { value: 'john', writable: false },
    age: { value: 30, writable: false },
  },
);

console.log(user); // {name: 'john', age: 30}
user.name = 'smith'; // Error

 

์—ฌ๋Ÿฌ ํ”„๋กœํผํ‹ฐ ์„ค๋ช…์ž ์กฐํšŒ — Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors ๋ฉ”์„œ๋“œ๋กœ ์„ค๋ช…์ž๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. defineProperties๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ์ฒด ๋ณต์‚ฌ์‹œ ํ”Œ๋ž˜๊ทธ๋„ ํ•จ๊ป˜ ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค(๋ฐ˜๋ณต๋ฌธ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ „๊ฐœ์—ฐ์‚ฐ์ž / Object.assign()์„ ์‚ฌ์šฉํ•œ ๊ฐ์ฒด ๋ณต์‚ฌ๋Š” ํ”Œ๋ž˜๊ทธ๋ฅผ ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š”๋‹ค).

const user = { name: 'john', age: 30 };
const copiedObj = Object.defineProperties(
  {},
  Object.getOwnPropertyDescriptors(user),
);

 

๊ฐ์ฒด ์ˆ˜์ •์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค


  `Object.preventExtensions` `Object.seal` `Object.freeze`
ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€ โŒ โŒ โŒ
ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ โœ… โŒ โŒ
ํ”„๋กœํผํ‹ฐ ์ˆ˜์ • โœ… โœ… โŒ
์ ์šฉ ์—ฌ๋ถ€ ํ™•์ธ `Object.isExtensible` `Object.isSealed` `Object.isFrozen`

 

  1. `Object.preventExtensions(obj)`
    • ์ƒˆ๋กœ์šด ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€ ๋ฐฉ์ง€
    • ์ ์šฉ ์—ฌ๋ถ€ ํ™•์ธ: `Object.isExtensible(obj)` — ์ ์šฉ ์•ˆํ–ˆ์œผ๋ฉด `true`
  2. `Object.seal(obj)`
    • ์ƒˆ๋กœ์šด ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€ / ๊ธฐ์กด ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ ๋ฐฉ์ง€
    • ํ”„๋กœํผํ‹ฐ ์ „์ฒด์— `configurable: false` ์„ค์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผ
    • ์ ์šฉ ์—ฌ๋ถ€ ํ™•์ธ: `Object.isSealed(obj)` — ์ ์šฉ ์•ˆํ–ˆ์œผ๋ฉด `false`
  3. `Object.freeze(obj)`
    • ์ƒˆ๋กœ์šด ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€ / ๊ธฐ์กด ํ”„๋กœํผํ‹ฐ ์‚ญ์ œ ๋ฐ ์ˆ˜์ • ๋ฐฉ์ง€
    • ํ”„๋กœํผํ‹ฐ ์ „์ฒด์— `configurable: false` (์‚ญ์ œ), `writable: false`(์ˆ˜์ •) ์„ค์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผ
    • ์ ์šฉ ์—ฌ๋ถ€ ํ™•์ธ: `Object.isFrozen(obj)` — ์ ์šฉ ์•ˆํ–ˆ์œผ๋ฉด `false`

 

โญ๏ธ ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ์™€ Object.defineProperty ํ™œ์šฉ


๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ๋Š” โžŠ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ โž‹์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ 2์ข…๋ฅ˜๋กœ ๋‚˜๋‰œ๋‹ค. ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ๋ฅผ ์ œ์™ธํ•œ ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋Š” ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ๋‹ค. ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ๋Š” ํ•จ์ˆ˜๋กœ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ’์„ ํš๋“(get)ํ•˜๊ณ  ์„ค์ •(set)ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค. ์™ธ๋ถ€ ์ฝ”๋“œ์—์„  ํ•จ์ˆ˜๊ฐ€ ์•„๋‹Œ ์ผ๋ฐ˜ ํ”„๋กœํผํ‹ฐ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค

 

๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ์˜ ์„ค๋ช…์ž์™€ ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ์˜ ์„ค๋ช…์ž๋Š” ๋‹ค๋ฅด๋‹ค. ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ์—” value writable ์„ค๋ช…์ž๊ฐ€ ์—†๋Š” ๋Œ€์‹ , get set์ด๋ผ๋Š” ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค. getter, setter ๊ด€๋ จ ๋…ธํŠธ๋Š” ๋งํฌ ์ฐธ๊ณ .

 

์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ๋Š” ์•„๋ž˜ ์„ค๋ช…์ž๋ฅผ ๊ฐ–๋Š”๋‹ค.

get ์ธ์ž๊ฐ€ ์—†๋Š” ํ•จ์ˆ˜. ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฝ์„ ๋•Œ ์‚ฌ์šฉ
set ์ธ์ž๊ฐ€ ํ•˜๋‚˜์ธ ํ•จ์ˆ˜. ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์“ธ ๋•Œ ์‚ฌ์šฉ
enumerable ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ์™€ ๋™์ผ
configurable ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ์™€ ๋™์ผ

 

defineProperty() ์„ค๋ช…์ž ํŒŒ๋ผ๋ฏธํ„ฐ์— get set(์„ค๋ช…์ž)๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์•„๋ž˜์ฒ˜๋Ÿผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

const user = { firstName: 'Color', lastName: 'Filter' };

Object.defineProperty(user, 'fullName', {
  // get() ํ•จ์ˆ˜์—” return ๋ฌธ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด๋ฏ€๋กœ this๋Š” ๊ฐ์ฒด(user)
  get() {
    return `${this.firstName} ${this.lastName}`;
  },
  set(value) {
    [this.firstName, this.lastName] = value.split(' ');
  },
});

console.log(user.fullName); // [get] 'Color Filter'
user.fullName = 'Jack Ma'; // [set]
console.log(user.fullName); // [get] 'Jack Ma'

for (const key in user) console.log(key); // 'firstName', 'lastName'

 

โ—๏ธ ํ”„๋กœํผํ‹ฐ๋Š” ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ(get/set)๋‚˜ ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ(value)์ค‘ ํ•œ ์ข…๋ฅ˜์—๋งŒ ์†ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•œ ํ”„๋กœํผํ‹ฐ์— โžŠget ํ˜น์€ set๊ณผ โž‹value๋ฅผ ๋™์‹œ์— ์„ค์ •ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

// Error: Invalid property descriptor.
// name ํ”„๋กœํผํ‹ฐ์— ์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ(get)์™€ ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ(value)๋ฅผ ๋™์‹œ์— ์„ค์ •ํ–ˆ์œผ๋ฏ€๋กœ ์—๋Ÿฌ ๋ฐœ์ƒ
Object.defineProperty({}, 'name', {
  get() {
    return 1;
  },
  value: 1,
});

 

์•„๋ž˜์ฒ˜๋Ÿผ ์ƒ์„ฑ์ž ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  Object.defineProperty(this, 'age', {
    // ์—ฌ๊ธฐ์„œ this๋Š” ์ธ์Šคํ„ด์Šค
    get() {
      let todayYear = new Date().getFullYear(); // 2021
      return todayYear - this.birthday.getFullYear();
    },
  });
}

const smith = new User('Smith', new Date(1988, 8, 8));
smith.age; // 33

 

์ฝ๊ธฐ ์ „์šฉ ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ


์•„๋ž˜ ๊ฐ™์€ ์›๋ณธ ๊ฐ์ฒด(๋ฐ์ดํ„ฐ)๊ฐ€ ์žˆ์„ ๋•Œ, โžŠ์›๋ณธ ๊ฐ์ฒด ์ž์ฒด๋ฅผ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜, โž‹์›๋ณธ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ์ฝ๊ธฐ ์ „์šฉ ๊ฐ์ฒด๋ฅผ ๋ณ„๋„๋กœ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ๋‹ค.

const state = { name: 'johan', age: 30 };

 

์›๋ณธ ๊ฐ์ฒด๋ฅผ ์ฝ๊ธฐ์ „์šฉ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค๊ธฐ

Object.defineProperty() ๋ฉ”์„œ๋“œ์— ํ”Œ๋ž˜๊ทธ ์ •๋ณด๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด ํ”Œ๋ž˜๊ทธ ๊ฐ’์€ ๋ชจ๋‘ false๊ฐ€ ๋œ๋‹ค. ์ด ๋ฐฉ๋ฒ•์„ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ(value)๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š๊ณ  get ์„ค๋ช…์ž(์ ‘๊ทผ์ž ํ”„๋กœํผํ‹ฐ)๋งŒ ๋‚จ๊ธฐ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ๊ฐ€ ์—†์œผ๋ฉด writable ํ”Œ๋ž˜๊ทธ๋„ ์กด์žฌํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

Object.keys(state).forEach((key) => {
  const _value = state[key];
  Object.defineProperty(state, key, {
    get() {
      return _value;
    },
  });
});

state; // {} ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ(value)๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋นˆ ๊ฐ์ฒด๋กœ ๋‚˜์˜ด
state.name; // [get] 'johan'
state.name = 'smith'; // [set] set ์„ค๋ช…์ž๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
state.name; // [get] 'johan'

Object.getOwnPropertyDescriptors(state);
// age: {set: undefined, enumerable: true, configurable: true, get: ƒ}
// name: {set: undefined, enumerable: true, configurable: true, get: ƒ}

 

์›๋ณธ ๊ฐ์ฒด์˜ writable ํ”Œ๋ž˜๊ทธ๋ฅผ false๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€/์‚ญ์ œ/์ˆ˜์ •๊นŒ์ง€ ๋ชจ๋‘ ๋ฐฉ์ง€ํ•˜๊ณ  ์‹ถ์œผ๋ฉด `Object.freeze()` ๋ฅผ ์“ฐ๋ฉด ๋œ๋‹ค.

Object.keys(state).forEach((key) => {
  Object.defineProperty(state, key, {
    writable: false,
  });
});

state; // {name: 'johan', age: 30}
state.name; // [[Get]] 'johan'
state.name = 'smith'; // [[Set]] writable ํ”Œ๋ž˜๊ทธ๊ฐ€ false์ด๋ฏ€๋กœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
state.name; // [[Get]] 'johan'

Object.getOwnPropertyDescriptors(state);
// age: {value: 30, writable: false, enumerable: true, configurable: true}
// name: {value: 'johan', writable: false, enumerable: true, configurable: true}

 

์›๋ณธ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ์ฝ๊ธฐ ์ „์šฉ ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ

์›๋ณธ ๊ฐ์ฒด state๋ฅผ ์ฐธ์กฐํ•˜๋Š” frozenState๋Š” getter ์ ‘๊ทผ์ž๋งŒ ์žˆ์œผ๋ฏ€๋กœ ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅํ•˜๊ณ , ์“ฐ๊ธฐ๋Š” ๋ถˆ๊ฐ€ํ•˜๋‹ค.

const frozenState = {}; // ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ๋งŒ๋“ค ๊ฐ์ฒด

Object.keys(state).forEach((key) => {
  Object.defineProperty(frozenState, key, {
    get() {
      return state[key];
    },
  });
});

frozenState; // {} ๋ฐ์ดํ„ฐ ํ”„๋กœํผํ‹ฐ(value)๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋นˆ ๊ฐ์ฒด๋กœ ๋‚˜์˜ด
frozenState.name; // [get] 'johan'
frozenState.name = 'smith'; // [set] set ์„ค๋ช…์ž๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Œ
frozenState.name; // [get] 'johan'

 

โญ๏ธ ์˜ต์„œ๋ฒ„ ํŒจํ„ด


์˜ต์„œ๋ฒ„ ํŒจํ„ด์„ ์ด์šฉํ•ด ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•˜๋Š” ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ์ƒํƒœ์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋ฆฌ๋ Œ๋” ๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. Vanilla JavaScript๋กœ React, Vue ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŒจํ„ด.

 

๊ธฐ๋ณธ

Object.defineProperty ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๊ฐ์ฒด์— ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธธ ๋•Œ ํŠน์ • ์•ก์…˜์„ ์ทจํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜์ฒ˜๋Ÿผ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฐ์ฒด์˜ ๊ฐ’์„ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋Š” ํ–‰๋™์„ getter์™€ setter๊ฐ€ ํ†ต์ œ ํ•˜๋„๋ก ํ•˜๊ณ , ์‹ค์งˆ์ ์ธ ๊ฐ’(๋ฐ์ดํ„ฐ)๋Š” ๋‚ด๋ถ€ ๋ณ€์ˆ˜์ธ _value์— ์ €์žฅ๋œ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ์—์„œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค observer ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค. observer๋Š” ๊ฐ์ฒด์˜ ๊ฐ’ ๋ณ€๊ฒฝ์„ ๊ฐ์‹œํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋ฉฐ, ์ด๋ฅผ ํ™œ์šฉํ•ด observer ํ•จ์ˆ˜์— ๋‹ค์–‘ํ•œ ์•ก์…˜์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ฝ”๋“œ ์ฐธ๊ณ : ๊ฐœ๋ฐœ์ž ํ™ฉ์ค€์ผ

const state = {
  a: 10,
  b: 20,
};

const stateKeys = Object.keys(state); // ['a', 'b']
const observer = () => console.log(`a + b = ${state.a + state.b}`);

for (const key of stateKeys) {
  let _value = state[key]; // state['a']/['b'] ๊ฐ’์„ _value์— ํ• ๋‹น
  Object.defineProperty(state, key, {
    get() {
      return _value;
    },
    set(value) {
      _value = value;
      observer(); // ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค observer ํ•จ์ˆ˜ ์‹คํ–‰
    },
  });
}

console.log(state.a); // [get] 10
state.a = 100; // [set] a + b = 120
state.b = 200; // [set] a + b = 300

 

๋‹ค์ˆ˜์˜ observer ๊ด€๋ฆฌ

์•„๋ž˜ ์ฝ”๋“œ์˜ ํ•ต์‹ฌ์€ addNum ํ˜น์€ subNum ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ๋•Œ currentObserver๊ฐ€ ์‹คํ–‰์ค‘์ธ ํ•จ์ˆ˜๋ฅผ ์ฐธ์กฐํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ. `observers`๋Š” Set ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ค‘๋ณต์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์กฐํšŒํ•  ๋•Œ ์ด๋ฏธ `observers`์— ์ถ”๊ฐ€ํ–ˆ๋˜ ํ•จ์ˆ˜๋ผ๋ฉด ์ค‘๋ณตํ•ด์„œ ์ถ”๊ฐ€๋˜์ง€ ์•Š๋Š”๋‹ค.

 

  • `state`์˜ ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์กฐํšŒ(get)ํ•  ๋•Œ `currentObserver`๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ `observers`์— ๋“ฑ๋กํ•œ๋‹ค.
  • `state`์˜ ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ๋ณ€๊ฒฝ(set)ํ•  ๋•Œ `observers`์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

 

์ฝ”๋“œ ์ฐธ๊ณ : ๊ฐœ๋ฐœ์ž ํ™ฉ์ค€์ผ

let currentObserver = null;

const state = {
  a: 10,
  b: 20,
};

const stateKeys = Object.keys(state);

for (const key of stateKeys) {
  let _value = state[key];
  const observers = new Set();

  Object.defineProperty(state, key, {
    get() {
      if (currentObserver) observers.add(currentObserver); // ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ๋“ฑ๋ก(addNum, subNum)
      return _value;
    },
    set(value) {
      _value = value;
      observers.forEach(observer => observer()); // ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ์‹คํ–‰(addNum, subNum)
    },
  });
}

const addNum = () => {
  // ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜
  currentObserver = addNum;
  console.log(`a + b = ${state.a + state.b}`);
};

const subNum = () => {
  // ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜
  currentObserver = subNum;
  console.log(`a - b = ${state.a - state.b}`);
};

addNum(); // [get] a + b = 30 (๊ฐ’์„ ์กฐํšŒํ•  ๋•Œ addNum ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๊ฐ€ ๋“ฑ๋ก๋จ)
state.a = 100; // [set] a + b = 120 (๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๊ณ , ๋“ฑ๋กํ•œ ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋จ)
subNum(); // [get] a - b = 80 (๊ฐ’์„ ์กฐํšŒํ•  ๋•Œ subNum ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๊ฐ€ ๋“ฑ๋ก๋จ)
state.b = 25; // [set] a + b = 125(addNum ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ์‹คํ–‰), a - b = 75(subNum ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ์‹คํ–‰)

 

โญ๏ธ ์žฌ์‚ฌ์šฉ์„ ์œ„ํ•œ ํ•จ์ˆ˜ํ™”

์—ฌ๋Ÿฌ ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก observe ํ•จ์ˆ˜๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ , observable ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ(์ƒํƒœ) ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ๊ฐ’์„ ์กฐํšŒํ•  ๋• ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋• ๋“ฑ๋กํ•œ ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ํ•œ๋‹ค.

 

  • `observe` : ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ ๋“ฑ๋กํ•˜๋Š” ์—ญํ• 
  • `observable` : ๋ฐ์ดํ„ฐ(๊ฐ์ฒด)๋ฅผ ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• 

 

์ฝ”๋“œ ์ฐธ๊ณ : ๊ฐœ๋ฐœ์ž ํ™ฉ์ค€์ผ

let currentObserver = null;

const observe = (fn) => {
  currentObserver = fn;
  fn();
  currentObserver = null;
};

const observable = (obj) => {
  Object.keys(obj).forEach((key) => {
    let _value = obj[key];
    const observers = new Set(); // ๊ฐ key๊ฐ€ observers(Set ๊ฐ์ฒด)๋ฅผ ํ•˜๋‚˜์”ฉ ๊ฐ€์ง„๋‹ค

    Object.defineProperty(obj, key, {
      get() {
        if (currentObserver) observers.add(currentObserver); // ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ๋“ฑ๋ก
        return _value;
      },

      set(value) {
        _value = value;
        observers.forEach(observer => observer()); // ๋“ฑ๋กํ•œ ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ๋ชจ๋‘ ์‹คํ–‰
      },
    });
  });
  return obj;
};

const state = observable({ a: 10, b: 20 }); // ๋ฐ์ดํ„ฐ(๊ฐ์ฒด)๋ฅผ ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
observe(() => console.log(`a + b = ${state.a + state.b}`)); // [get] ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ 1 ๋“ฑ๋ก
state.a = 100; // [set] a + b = 120(์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ 1 ์‹คํ–‰)

observe(() => console.log(`a - b = ${state.a - state.b}`)); // [get] ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ 2 ๋“ฑ๋ก
state.b = 50; // [set] a + b = 150(์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ 1 ์‹คํ–‰), a - b = 50(์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ 2 ์‹คํ–‰)

 

  1. `observable({ a: 10, b: 20 })`
    ๊ฐ์ฒด(๋ฐ์ดํ„ฐ)์˜ ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ — ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๊ฐ€ `observers` ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง
  2. `observe(() => console.log(...))`
    • `observe` ํ•จ์ˆ˜๊ฐ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์€ ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๋ฅผ `currentObserver` ๋ณ€์ˆ˜์— ๋“ฑ๋ก
    • ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ์‹คํ–‰
      • ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜์—์„œ ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์กฐํšŒํ•˜๋ฏ€๋กœ ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ์˜ `get()` ๋ฉ”์„œ๋“œ ์‹คํ–‰
      • `currentObserver` ๋ณ€์ˆ˜์— ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ `observers` ๊ฐ์ฒด์— ์˜ต์„œ๋ฒ„ ๋“ฑ๋ก
      • ํ”„๋กœํผํ‹ฐ ๊ฐ’ ๋ฐ˜ํ™˜
    • `currentObserver` ๋ณ€์ˆ˜ `null`๋กœ ๋ณ€๊ฒฝ
  3. `state.a = 100`
    • `a` ํ”„๋กœํผํ‹ฐ ๊ฐ’ ๋ณ€๊ฒฝ
    • ๋“ฑ๋กํ•œ ์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜ ๋ชจ๋‘ ์‹คํ–‰

 

โญ๏ธ Proxy๋ฅผ ์‚ฌ์šฉํ•œ ์˜ต์„œ๋ฒ„ ํŒจํ„ด

Proxy๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด observable ํ•จ์ˆ˜๋ฅผ ๋” ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

// ...์ƒ๋žต

const observable = (obj) => {
  const observerMap = {}; // { a: Set(...), b: Set(...) } ์˜ต์„œ๋ฒ„ ๊ฐ์ฒด

  return new Proxy(obj, {
    get(target, prop) {
      observerMap[prop] = observerMap[prop] || new Set();
      // ๋ชจ๋“  prop์ด ์˜ต์„œ๋ฒ„(Set) ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง„๋‹ค
      if (currentObserver) observerMap[prop].add(currentObserver);
      return target[prop];
    },
    set(target, prop, value) {
      target[prop] = value;
      observerMap[prop].forEach(fn => fn()); // Set ๊ฐ์ฒด ์ž์ฒด์ ์œผ๋กœ forEach ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค
      return true; // set ํŠธ๋žฉ์„ ์‚ฌ์šฉํ•  ๋• ํ•ญ์ƒ true(์„ฑ๊ณต) ํ˜น์€ false(์‹คํŒจ)๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค
    },
  });
};
๋”๋ณด๊ธฐ

๐Ÿ” 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

 

์˜ต์„œ๋ฒ„ ํ•จ์ˆ˜์˜ ๋ถˆํ•„์š”ํ•œ ์‹คํ–‰ ๋ฐฉ์ง€

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

 

`Object.definedProperty` ์‚ฌ์šฉ ์‹œ

set(value) {
  if (_value === value) return; // ์›์‹œํ˜•
  if (JSON.stringify(_value) === JSON.stringify(value)) return; // ์ฐธ์กฐํ˜•
    // ...
},

 

Proxy ์‚ฌ์šฉ ์‹œ

set(target, prop, value) {
  if (target[prop] === value) return true; // ์›์‹œํ˜•
  if (JSON.stringify(target[prop]) === JSON.stringify(value)) return true; // ์ฐธ์กฐํ˜•
    // ...
},

 

๋ ˆํผ๋Ÿฐ์Šค


๋ฐ˜์‘ํ˜•