Skip to content

Commit 2dd0e27

Browse files
committed
✏️ 4회차 문서 수정 : 프로퍼티 어트리뷰트
1 parent b328e62 commit 2dd0e27

2 files changed

Lines changed: 200 additions & 1 deletion

File tree

169 KB
Loading

Javascript/property_attribue.md

Lines changed: 200 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,205 @@
1-
객체를 다루면서 프로퍼티(Property)에 대해 어느정도 알고 있었죠? 이제 그 프로퍼티를 뜯어봅니다!
1+
객체를 다루면서 프로퍼티(Property)에 대해 어느정도 알고 있었죠? 이제 그 프로퍼티를 뜯어봅니다! 하지만 반드시 알아야 하는 내용을 내포하진 않으므로, 필요에 의한 참조로 접근해주세요.
22

33
## 프로퍼티 어트리뷰트(Property Attribute)
4+
> 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트(Property Attribute)를 기본값으로 자동 정의합니다.
5+
6+
그 전에, 내부 슬롯(Internal Slot)과 내부 메서드(Internal Method)에 대해 짚고 넘어가죠. 내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티(Pseudo Property)와 의사 메서드(Pseudo Method)입니다.
7+
8+
<br>
9+
10+
<div align='center'>
11+
12+
<img src='./img/property_attribue/ecmascript.png' width='600'/>
13+
14+
</div>
15+
16+
<br>
17+
18+
위 그림처럼 이중 대괄호(`[[]]`)로 감싼 이름들이 내부 슬롯과 내부 메서드입니다. 원칙적으로 자바스크립트는 내부 슬롯과 내부 메서드에 직접적인 접근 및 호출을 금지하고 있습니다. 단 일부에 한해 간접적으로 접근 가능한 수단을 제공합니다.
19+
20+
예를 들어 모든 객체는 `[[Prototype]]`이라는 내부 슬롯을 갖고, `__proto__`를 통해 간접적으로 접근할 수 있습니다. 아래 처럼요!
21+
22+
```js
23+
const obj = {};
24+
25+
obj.[[Prototype]]; // SyntaxError: Unexpected token '['
26+
obj.__proto__; // {constructor: ƒ, …}
27+
```
28+
29+
<br>
30+
31+
자, 그럼 프로퍼티 어트리뷰트에 대해 알아볼 차례입니다.
32+
33+
### 프로퍼티 어트리뷰트와 디스크립터 객체
34+
> 자바스크립트 엔진이 관리하는 내부 상태 값(Meta Propert)인 내부 슬롯입니다.
35+
36+
프로퍼티 어트리뷰트에 직접 접근할 수는 없습니다. 이유는 상술했죠? 그러나 `Object.getOwnPropertyDescriptor` 메서드로 간접적인 확인이 가능합니다. 이 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 **프로퍼티 디스크립터(Property Descriptor)** 객체를 반환합니다. 만약 존재하지 않는 프로퍼티에 대해 디스크립터를 요구하면 `undefined`가 반환됩니다.
37+
38+
```js
39+
const person = {
40+
name : 'amy'
41+
};
42+
person.age = 16;
43+
console.log(Object.getOwnPropertyDescriptors(person));
44+
/*
45+
{
46+
name: { value: 'amy', writable: true, enumerable: true, configurable: true },
47+
age: { value: 16, writable: true, enumerable: true, configurable: true }
48+
}
49+
*/
50+
```
51+
52+
ES8에는 복수의 프로퍼티 어트리뷰트 정보를 제공하는 `getOwnPropertyDescriptors` 함수도 추가되었지만... 알아만 두세요!
53+
54+
프로퍼티는 데이터 프로퍼티(Data Property)와 접근자 프로퍼티(Accessor Property)로 구분합니다.
55+
56+
- 데이터 프로퍼티는 키와 값으로 구성되고 지금껏 살펴본 모든 프로퍼티가 여기에 해당하며, 자바스크립트 엔진이 프로퍼티를 생성할 때 기본값으로 자동 정의합니다.
57+
| 프로퍼티 어트리뷰트 | 설명 |
58+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59+
| `[[Value]]` | ✅ 프로퍼티 키를 통해 프로퍼티 값을 접근하면 반환되는 값입니다.<br>✅ 프로퍼티 키를 통해 프로퍼티 값을 변경하면 `[[Value]]`에 값을 재할당합니다. 이 때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티 `[[Value]]`에 값을 저장합니다. |
60+
| `[[Writable]]` | ✅ 프로퍼티 값의 변경 가능 여부를 나타내며 boolean 값을 갖습니다.<br>✅ `[[Writable]]`이 false인 경우 해당 프로퍼티의 `[[Value]]` 값을 변경할 수 없는 읽기 전용 프로퍼티가 됩니다. |
61+
| `[[Enumerable]]` | ✅ 프로퍼티의 열거 가능 여부를 나타내며 boolean 값을 갖습니다.<br>✅ `[[Enumerable]]`의 값이 false인 경우 해당 프로퍼티는 for ... in 이나 Object.keys 메서드 등으로 열거할 수 없습니다. |
62+
| `[[Configurable]]` | ✅ 프로퍼티의 재정의 가능 여부를 나타내며 boolean값을 갖습니다.<br>✅ `[[Configurable]]`의 값이 false인 경우 삭제나 변경이 금지됩니다. 단, `[[Writable]]`이 true인 경우는 `[[Value]]`의 변경과 `[[Writable]]`을 false로 변경하는 것이 허용됩니다. |
63+
64+
- 접근자 프로퍼티는 자체적으로 값을 갖지 않으나 데이터 프로퍼티의 값을 읽거나 수정할 때 호출되는 접근자 함수(Accessor Function)로 구성된 프로퍼티입니다. 접근자 함수는 `getter/setter` 함수라고도 부릅니다.
65+
66+
| 프로퍼티 어트리뷰트 | 설명 |
67+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
68+
| `[[Get]]` | ✅ 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 읽을 때 호출되는 접근자 함수입니다.<br>✅ 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 `[[Get]]`의 값, getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환됩니다. |
69+
| `[[Set]]` | ✅ 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수입니다.<br>✅ 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 `[[Set]]`값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장됩니다. |
70+
| `[[Enumerable]]` | ✅ 데이터 프로퍼티의 enumerable과 같습니다. |
71+
| `[[Configurable]]` | ✅ 데이터 프로퍼티의 configurable과 같습니다. |
72+
- 접근자 프로퍼티로 프로퍼티 값에 접근하면 어떻게 동작할까요?
73+
- 1️⃣ 프로퍼티 키가 유효한지 확인합니다. 프로퍼티 키는 문자열이거나 심볼이어야 합니다.
74+
- 2️⃣ 프로토타입 체인에서 프로퍼티를 검색합니다.
75+
- 3️⃣ 검색된 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인합니다.
76+
- 4️⃣ 접근자 프로퍼티의 프로퍼티 어트리뷰트 `[[Get]]`의 값인 getter를 호출하여 그 결과를 반환합니다.
77+
78+
> 잠깐, 프로토타입 체인?
79+
- 어려운 단어가 나왔네요? 프로토타입 체인이라니. 프로토타입(Prototype)은 어떤 객체의 상위(부모) 객체 역할을 하는 `객체`입니다. 이 프로토타입을 통해 우리는 객체 지향 프로그램처럼 상속과 확장을 할 수 있죠. 프로토타입 체인(Prototype Chain)은 프로토타입이 단방향 링크드 리스트(Linked List)로 연결된 상속 구조를 뜻합니다.
80+
81+
<br>
82+
83+
### 프로퍼티 정의
84+
> 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티 어트리뷰트를 재정의하는 것입니다.
85+
86+
Object.defineProperty 메서드를 사용하면 프로퍼티 어트리뷰트를 정의할 수 있습니다.
87+
88+
```js
89+
const person = {};
90+
91+
Object.defineProperty(person, 'firstName', {
92+
value : 'Chil',
93+
writable : true,
94+
enumerable : true,
95+
configurable : true
96+
})
97+
98+
Object.defineProperty(person, 'lastName', {
99+
value : 'Amy',
100+
})
101+
102+
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
103+
console.log('firstName', descriptor);
104+
// firstName {
105+
// value: 'Chil',
106+
// writable: true,
107+
// enumerable: true,
108+
// configurable: true
109+
// }
110+
111+
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
112+
console.log('lastName', descriptor);
113+
// lastName {
114+
// value: 'Amy',
115+
// writable: false,
116+
// enumerable: false,
117+
// configurable: false
118+
// }
119+
console.log(Object.keys(person));
120+
//[ 'firstName' ]
121+
person.lastName = "J";
122+
delete person.lastName;
123+
124+
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
125+
console.log('lastName', descriptor);
126+
// lastName {
127+
// value: 'Amy',
128+
// writable: false,
129+
// enumerable: false,
130+
// configurable: false
131+
// }
132+
Object.defineProperty(person, 'fullName', {
133+
get() {
134+
return `${this.firstName} ${this.lastName}`
135+
}
136+
set(name){
137+
[this.firstName, this.lastName] = name.split(' ');
138+
}
139+
enumberable : true,
140+
configurable : true
141+
});
142+
143+
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
144+
console.log(descriptor);
145+
// {
146+
// get: [Function: get],
147+
// set: [Function: set],
148+
// enumerable: false,
149+
// configurable: true
150+
// }
151+
person.fullName = "thin Amy";
152+
console.log(person);
153+
// { firstName: 'thin' }
154+
```
155+
156+
프로퍼티 디스크립터 객체에서 생략된 어트리뷰트는 다음과 같은 기본값이 적용됩니다.
157+
158+
| 프로퍼티 디스크립터 객체의 프로퍼티 | 대응하는 프로퍼티 어트리뷰트 | 생략했을 때의 기본값 |
159+
| ----------------------------------- | ---------------------------- | -------------------- |
160+
| value | `[[Value]]` | undefined |
161+
| get | `[[Get]]` | undefined[ |
162+
| set | `[[Set]]` | undefined |
163+
| writable | `[[Writable]]` | false |
164+
| enumerable | `[[Enumerable]]` | false |
165+
| configurable | `[[Configurable]]` | false |
166+
167+
복수의 프로퍼티를 정의하는 `defineProperties` 함수도 존재하지만... 알아만 두세요!
168+
169+
<br>
170+
171+
### 객체 변경 방지
172+
> 객체는 변경 가능한 값이므로 재할당 없이 직접 변경할 수 있습니다. 이를 방지하려면 어떻게 할까요?
173+
174+
자바스크립트는 객체 변경 방지를 위해 아래와 같은 메서드를 제공합니다.
175+
176+
| 구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
177+
| -------------- | ------------------------ | :-----------: | :-----------: | :--------------: | :--------------: | :------------------------: |
178+
| 객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
179+
| 객체 밀봉 | Object.seal | X | X | O | O | X |
180+
| 객체 동결 | Object.freeze | X | X | O | X | X |
181+
182+
추가적으로 객체를 불변 객체로 만들기 위해서는 Object.freeze 메서드를 재귀적으로 호출해야 합니다.
183+
184+
```js
185+
function deepFreeze(target){
186+
if(target && typeof target === 'object' && !Object.isFrozen(target)){
187+
Object.freeze(target);
188+
Object.keys(target).forEach(key => deepFreeze(target[key]));
189+
}
190+
}
191+
const person = {
192+
name : 'Amy',
193+
address: {city : 'LA'}
194+
};
195+
196+
deepFreeze(person)
197+
console.log(Object.isFrozen(person)); // true
198+
console.log(Object.isFrozen(person.address)); // true
199+
200+
person.address.city = 'Seoul';
201+
console.log(person); // {name: 'Amy', address: {city: 'Seoul'}}
202+
```
4203

5204
<hr>
6205
<br>

0 commit comments

Comments
 (0)