[JS] ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด ํ๋กํผํฐ ์ค๋ช ์ / ํ๋๊ทธ / ์ต์๋ฒ ํจํด
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
ํ๋กํผํฐ ํ๋๊ทธ
๊ฐ์ฒด๋ ๊ฐ(value) ์ธ์๋ ํ๋๊ทธ(flag)๋ผ๋ ํน๋ณ์ ์์ฑ์ด ์๋ค. ํ๋๊ทธ๋ ์๋ 3๊ฐ์ง ์ข ๋ฅ๊ฐ ์๋ค. ์ผ๋ฐ์ ์ผ๋ก ๊ฐ์ฒด๋ฅผ ์ ์ธํ๋ฉด(๊ฐ์ฒด ๋ฆฌํฐ๋ด ํน์ Object ์์ฑ์ ํจ์ ์ฌ์ฉ) ํ๋กํผํฐ์ ํ๋๊ทธ๋ `true`๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๊ฐ์ง๋ค.
ํ๋กํผํฐ ๊ฐ ์์ | ํ๋กํผํฐ ์ญ์ | ๋ฐ๋ณต๋ฌธ ๋์ด | ํ๋๊ทธ ์์ | |
`writable: false` | โ | โ | โ | โ |
`enumerable: false` | โ | โ | โ | โ |
`configurable: false` | โ | โ | โ | โ |
- writable (์์ )
- `true` : ํ๋กํผํฐ ๊ฐ ์์ ๊ฐ๋ฅ
- `false` : ํ๋กํผํฐ ๊ฐ ์์ ๋ถ๊ฐ (ํ๋กํผํฐ ์ญ์ ๋ ๊ฐ๋ฅ)
- enumerable (์ด๊ฑฐ)
- `true` : ๋ฐ๋ณต๋ฌธ์ผ๋ก ๋์ด ๊ฐ๋ฅ
- `false` : ๋ฐ๋ณต๋ฌธ์ผ๋ก ๋์ด ๋ถ๊ฐ
- 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์ผ๋).
configurable
ํ๋๊ทธ ์์ ๋ถ๊ฐenumerable
ํ๋๊ทธ ์์ ๋ถ๊ฐwritable: false
๊ฐ์true
๋ก ๋ณ๊ฒฝ ๋ถ๊ฐ(true → false ๋ณ๊ฒฝ์ ๊ฐ๋ฅ)- ์ ๊ทผ์ ํ๋กํผํฐ
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` |
- `Object.preventExtensions(obj)`
- ์๋ก์ด ํ๋กํผํฐ ์ถ๊ฐ ๋ฐฉ์ง
- ์ ์ฉ ์ฌ๋ถ ํ์ธ: `Object.isExtensible(obj)` — ์ ์ฉ ์ํ์ผ๋ฉด `true`
- `Object.seal(obj)`
- ์๋ก์ด ํ๋กํผํฐ ์ถ๊ฐ / ๊ธฐ์กด ํ๋กํผํฐ ์ญ์ ๋ฐฉ์ง
- ํ๋กํผํฐ ์ ์ฒด์ `configurable: false` ์ค์ ํ๋ ๊ฒ๊ณผ ๋์ผ
- ์ ์ฉ ์ฌ๋ถ ํ์ธ: `Object.isSealed(obj)` — ์ ์ฉ ์ํ์ผ๋ฉด `false`
- `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 ์คํ)
- `observable({ a: 10, b: 20 })`
๊ฐ์ฒด(๋ฐ์ดํฐ)์ ๋ชจ๋ ํ๋กํผํฐ๋ฅผ ๊ด์ฐฐ ๊ฐ๋ฅํ ์ํ๋ก ๋ณ๊ฒฝ — ๋ชจ๋ ํ๋กํผํฐ๊ฐ `observers` ๊ฐ์ฒด๋ฅผ ๊ฐ์ง - `observe(() => console.log(...))`
- `observe` ํจ์๊ฐ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ์ต์๋ฒ ํจ์๋ฅผ `currentObserver` ๋ณ์์ ๋ฑ๋ก
- ์ต์๋ฒ ํจ์ ์คํ
- ์ต์๋ฒ ํจ์์์ ํ๋กํผํฐ ๊ฐ์ ์กฐํํ๋ฏ๋ก ํด๋น ํ๋กํผํฐ์ `get()` ๋ฉ์๋ ์คํ
- `currentObserver` ๋ณ์์ ์ต์๋ฒ ํจ์๊ฐ ๋ฑ๋ก๋์ด ์์ผ๋ฏ๋ก `observers` ๊ฐ์ฒด์ ์ต์๋ฒ ๋ฑ๋ก
- ํ๋กํผํฐ ๊ฐ ๋ฐํ
- `currentObserver` ๋ณ์ `null`๋ก ๋ณ๊ฒฝ
- `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`(๋ณ๊ฒฝํ)๋ฅผ ์ถ๋ ฅํ๋ค.
- `user` ๊ฐ์ฒด์ `observe` ํจ์ ๋ฑ๋ก → `obj[handlers]` ๋ฐฐ์ด์ ํจ์ ์ ์ฅ
- `user.name` ๊ฐ ์ฐ๊ธฐ ์๋
- `set` ํธ๋ฉ ์คํ ํ `name` ํ๋กํผํฐ ๊ฐ ๋ณ๊ฒฝ
- `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; // ์ฐธ์กฐํ
// ...
},
๋ ํผ๋ฐ์ค
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[HTML/CSS] ํผ ํ๋(input) ์์ ๋ณ๊ฒฝํ๊ธฐ โ accent-color (0) | 2024.04.29 |
---|---|
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ Proxy ๊ฐ์ฒด / Reflect (0) | 2024.04.29 |
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ๋๋ฐ์ด์ค Debounce, ์ค๋กํ Throttle ๊ตฌํํ๊ธฐ (0) | 2024.04.29 |
[Git] Github ๋งํฌ๋ค์ด์ ๊ฐ์ฃผ ๋ฌ๊ธฐ (0) | 2024.04.29 |
[React] ๋ฆฌ์กํธ ์์ ๋๋๊ทธ์ค๋๋กญ ๊ตฌํ (0) | 2024.04.28 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[HTML/CSS] ํผ ํ๋(input) ์์ ๋ณ๊ฒฝํ๊ธฐ — accent-color
[HTML/CSS] ํผ ํ๋(input) ์์ ๋ณ๊ฒฝํ๊ธฐ — accent-color
2024.04.29 -
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ Proxy ๊ฐ์ฒด / Reflect
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ Proxy ๊ฐ์ฒด / Reflect
2024.04.29 -
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ๋๋ฐ์ด์ค Debounce, ์ค๋กํ Throttle ๊ตฌํํ๊ธฐ
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ๋๋ฐ์ด์ค Debounce, ์ค๋กํ Throttle ๊ตฌํํ๊ธฐ
2024.04.29 -
[Git] Github ๋งํฌ๋ค์ด์ ๊ฐ์ฃผ ๋ฌ๊ธฐ
[Git] Github ๋งํฌ๋ค์ด์ ๊ฐ์ฃผ ๋ฌ๊ธฐ
2024.04.29