JavaScript Object 탐구생활
이 글에선 JavaScript Object 기초에 대해 설명하지 않습니다. 대신 Object에 대해 깊게 알아보도록 하겠습니다.
자바스크립트 객체는 프로퍼티의 모음이며, 프로퍼티는 “이름”(name 또는 key)과 “값”(value)의 연결로 이루어진다 — MDN 발췌
Property 타입
JavaScript의 Object는 두 가지의 property 타입이 있습니다. 아래 방식은 흔히 Object를 생성할 때 사용하는 코드입니다. 이때 name
, age
property를 Data Property
라 부릅니다.
const o = {
name: 'seo jeong kuk',
age: 31
}
JavaScript에서 객체를 생성할 때 getter와 setter를 설정할 수 있습니다. 아래 코드에서 name
, age
property를 Accessor Property
라 부릅니다.
const accessor = {
get name() {
return 'seo jeong kuk'
},
get age() {
return 31
}
}console.log(accessor.name) // seo jeong kuk
아래는 위 코드에 setter 까지 추가한 code 입니다.
내부 Property
일반적으로 Object는 key-value 쌍으로 1:1 맵핑 관계로 보여지지만, 각 key에는 value 특성을 정의하는 property 속성들이 있습니다. 아래에 총 6개의 속성들을 정리했습니다.
- [[Value]]
- [[Get]]
- [[Set]]
- [[Writable]]
- [[Enumerable]]
- [[Configurable]]
ECMA 스펙에 따라 내부 Property
는 [[]]으로 표시 됩니다. 이 Property들은 일반적인 방법으론 접근할 수 없으며 Object와 관련된 method를 사용해야 합니다.
예를 들어 {x: 5, y: 6} 인 객체가 있을 때 내부 Property 속성들은 아래와 같은 값을 가지고 있습니다.
코드에서 실제로 확인하기 위해선 Object.getOwnPropertyDescriptor
를 사용해야 합니다. 아래 코드를 실행한 결과를 보겠습니다.
const object = {
x: 5,
y: 6
};console.log(Object.getOwnPropertyDescriptor(object, 'x'))// 결과
{
value: 5,
writable: true,
enumerable: true,
configurable: true
}
왜 결과에 6개 전부가 나오지 않는건지, 각 속성은 뭘 의미하는지 알아보도록 하겠습니다.
- [[Value]]
객체 key에 해당되는 실제 value를 저장합니다. 위 코드처럼object.x
방식이나object["x"]
로 값에 접근하면 실제로는 [[Value]] 속성에 접근하게 됩니다. Data Property의 경우에만 이 방식으로 동작합니다. - [[GET]]
위에서 getter property를 사용하여 선언한 함수의 레퍼런스를 저장합니다. - [[SET]]
위에서 setter property를 사용하여 선언한 함수의 레퍼런스를 저장합니다. 할당된 value가 argument로 전달됩니다.
const object = {
set x(value) {
// value is 3
}
};object.x = 3 // x에 할당한 3이 argument로 전달됨
- [[Writable]]
boolean 값으로true
면 변경가능하며false
면 property 값이 변경되지 않습니다. - [[Enumerable]]
boolean 값으로 for-in loop에 나타날지 결정합니다.true
면 for-in loop 반복자가 property에 접근할 수 있습니다. - [[Configurable]]
boolean 값으로false
인 경우 Object의 property를 삭제할 수 없습니다. 그리고enumerable
,configurable
,get
,set
이 고정되어 변경할 수 없게 됩니다.
총 6개의 property는 property type에 따라 다르게 나타납니다. 일반적으로 사용하는 Data Property의 경우 value
, writable
, enumerable
, configurable
가 존재합니다. 만약 Accessor Property를 사용했다면 get, set
, enumerable
, configurable
가 존재합니다.
이 내부 Property를 변경하기 위해서 Object.defineProperty
메소드를 사용할 수 있습니다. 이 메소드를 활용해 다시금 property의 기능을 확인하도록 하겠습니다.
위 코드에서 3번 라인에서 obj
객체의 id
property의 value 속성을 42로 할당했습니다. 헌데 왜 7번 line에서 console 결과가 {} 로 나올까요?
한 가지 알아야 할 것은 Object.defineProperty
를 사용할 때 명시하지 않은 속성은 false가 default 값이 됩니다. 즉 enumerable
속성이 false
이기 때문에 console 결과에 출력되지 않습니다. 하지만 8번 line에서 id 속성에 직접 접근했을 때는 정상적으로 접근하게 됩니다.
9번 line에서 데이터 변경이 되지 않는 이유도 마찬가지로 writable
속성이 default로 false가 되어 값이 변경되지 않습니다.
그렇다면 id property 속성을 다시 Object.defineProperty
로 변경해봅시다. 위 코드에 아래 코드를 아래에 복사해서 실행해봅시다.
Object.defineProperty(obj, 'id', {
value: 100,
enumerable: true
});
이러면 아래와 같은 에러가 발생합니다.
TypeError: Cannot redefine property: id
이 에러가 발생하는 이유는 처음 id property가 configurable: false
로 설정되었기 때문입니다. 이 값이 false가 되면 enumerable
, configurable
, get
, set
이 고정되어 변경할 수 없게 된다고 위에서 설명했습니다.
그렇다면 처음부터 속성을 변경
해서 다시 실행하도록 하겠습니다.
writable
속성이 true이기 때문에 변경도 가능하며 configurable
속성도 true기 때문에 enumerable
속성을 중간에 true로 변경하여 18번 line에서 정상적으로 출력하였습니다.
Object 보호하기
그렇다면 객체의 변경을 막으려 할 때, 위에서 설명한 writable
, enumerable
, configurable
을 일일히 false로 선언해야 할까요?
JavaScript는 객체를 보호하기 위해 3가지의 메소드를 제공합니다.
Object.preventExtensions
객체에 새로운 property가 추가되지 않도록 합니다. 변경과 삭제는 가능합니다.
const obj = {
name: 'seo jeong kuk'
}
Object.preventExtensions(obj);obj.age = 31; // 동작하지 않음
console.log(obj) // {name: 'seo jeong kuk'}console.log(Object.isExtensible(obj)) // false
Object.seal
객체의 property 추가/삭제를 막습니다.
const obj = {
name: 'seo jeong kuk'
}Object.seal(obj);obj.age = 31; // 동작하지 않음
console.log(obj) // {name: 'seo jeong kuk'}delete obj.name; // 동작하지 않음
console.log(obj) // {name: 'seo jeong kuk'}console.log(Object.isExtensible(obj)) // false
console.log(Object.isSealed(obj)) // true
Object.freeze
객체를 제한할 수 있는 가장 강력한 방법으로 property 추가/수정/삭제를 막습니다. 내부적으로 Object.seal
을 호출하고 property 변경을 막습니다.
const obj = {
name: 'seo jeong kuk'
}Object.freeze(obj);obj.age = 31; // 동작하지 않음
console.log(obj) // {name: 'seo jeong kuk'}delete obj.name; // 동작하지 않음
console.log(obj) // {name: 'seo jeong kuk'}obj.name = 'SEO JEONG KUK' // 동작하지 않음
console.log(obj) // {name: 'seo jeong kuk'}console.log(Object.isExtensible(obj)) // false
console.log(Object.isSealed(obj)) // true
console.log(Object.isFrozen(obj)) // true
위 3개의 메소드를 비교한 결과입니다.
조회 추가 수정 삭제
preventExtensions O X O O
seal O X O X
freeze O X X X