객체 프로퍼티란?
객체에서는 key:value 쌍으로 프로퍼티가 저장되며
각각을 key : 프로퍼티 이름, value : 프로퍼티 값 으로도 부른다.
그러므로, key:value의 한 쌍을 프로퍼티라고 생각하면 되겠다.
이 포스팅에선 객체 프로퍼티의 특수한 옵션인 플래그(Flag)와 설명자(Descrpitor)라는 강력한 기능을 알아본다.
프로퍼티 플래그
프로퍼티 플래그는 writable, enumarable, configurable의 3가지 속성으로 구성된다.
각각의 개념은 다음과 같다.
writable
값 수정 가능성.
true일 시 값을 수정할 수 있으며, false일 때는 읽기만 가능하다.
enumerable
나열 가능성
ture일 시 반복문 등을 통해 나열이 가능하며, false일 때는 나열이 불가능하다.
configurable
설정 가능성.
true일 시 프로퍼티 삭제 / 플래그 수정이 가능하며, false일 때는 프로퍼티 삭제 플래그 수정이 불가능하다.
프로퍼티 플래그 확인하기
객체의 프로퍼티들은 각각 플래그를 가지고 있다.
기본적으로 리터럴{}로 생성한 객체에서는, 각각 프로퍼티 플래그는 모두 true 값을 가진다.
프로퍼티 플래그를 확인하기 위해서는Object.getOwnPropertyDescriptor
를 사용하며,
메서드의 인자로 (대상 객체, 프로퍼티 이름)을 넣어주면 된다.
const obj = {
name : 'Tom',
age : 23,
};
Object.getOwnPropertyDescriptor(obj, 'name');
getOwnPropertyDescriptor
메서드의 결과로 새로운 객체가 리턴되는데, 이를 프로퍼티 설명자라고 한다.
그림과 같이 프로퍼티 설명자는 프로퍼티 값, 3가지 프로퍼티 플래그의 불린값을 담고 있다.
각 프로퍼티마다 이 프로퍼티 설명자가 존재하며, 플래그나 프로퍼티 값을 변경하면 설명자의 값이 변경된다.
프로퍼티 값, 플래그 변경하기
Object.definedProperty
로 프로퍼티 값과 플래그를 변경할 수 있으며,
메서드의 인자로는 (대상 객체, 프로퍼티 이름, 변경하려는 설명자의 프로퍼티 이름과 값) 을 넣어준다.
이 때 변경하려는 프로퍼티가 있다면 변경을 수행하며,
프로퍼티가 없다면 프로퍼티를 생성하며 플래그 값은 기본적으로 false가 된다.
const obj = {
name:'Tom'
};
Object.defineProperty(obj, 'name', {value:'James'});
Object.getOwnPropertyDescriptor(obj, 'name');
const obj2 = {};
Object.defineProperty(obj2, 'name', {value:'Tom'});
Object.getOwnPropertyDescriptor(obj2, 'name');
obj는 프로퍼티 name을 가지고 있으며, obj2는 프로퍼티 name를 가지고 있지 않다.
obj는 설명자에서 value 값만 바뀌었으며
obj2는 설명자에서 value 값을 생성하면서 플래그값이 모두 false로 세팅되었다.
obj2.name = 'Tom';
위와 같은 방식으로 새로운 프로퍼티를 만들면 플래그 기본값이 true이지만,
definedProperty 메서드를 이용하면 플래그 기본값은 false임을 주의한다.
만약 definedProperty를 이용하면서 플래그를 true로 설정하고 싶다면, 3번째 인자값에 추가로 부여하면 된다.
Object.defineProperty(obj2, 'name', {
value:'Tom',
writable:true,
enumerable:true,
configurable:true
}
);
플래그 살펴보기
다음은 3가지 플래그들의 동작을 코드를 통해 알아보겠다.
Writable
값 수정 가능성.
true일 시 값을 수정할 수 있으며, false일 때는 읽기만 가능하다.
const obj = {
name:'Tom'
};
Object.defineProperty(obj, 'name', {writable:false} );
obj.name = 'James';
obj;
obj 객체의 name 프로퍼티의 '값 수정 가능성'을 false로 변경하여 읽기만 가능하다.
writable:false 속성은 'strict mode'에서만 오류를 발생시킨다.
그래서 원래대로라면 obj.name='James' 코드에서 오류를 발생시켜야 하지만,
'sloppy mode'를 기본으로 하는 브라우저(크롬)의 콘솔에서 작업했기 때문에, 변경 작업은 무시되었다.
브라우저 콘솔에서 strict mode를 사용하고 싶다면 다음을 참조하길 바란다.
아무튼 obj 객체를 조회했을 때, name:'Tom' 값이 변경되지 않았음을 볼 수 있다.
Enumerable
나열 가능성
ture일 시 반복문 등을 통해 나열이 가능하며, false일 때는 나열이 불가능하다.
객체의 프로퍼티를 나열하기 위해서, for in 문을 사용해본다.
const obj = {
name:'Tom',
age:23,
hello(){
return 'world'
},
}
for(let i in obj){
console.log(i);
}
Object.defineProperty(obj, 'hello', {enumerable:false});
for(let i in obj){
console.log(i);
}
반복문에서는 enumerable:false인 요소를 불러오지 않는다.
그러므로 두번째 for 문에서는, hello 프로퍼티가 조회되지 않는다.
주의할 점으로는
Object.keys를 이용해 프로퍼티 키들을 배열로서 얻고자 할 때
enumerable:false인 값은 역시 조회되지 않음에 유의한다.
entries, keys, values 메서드 모두 열거를 통해 배열 원소에 키를 넣기 때문에,
enumarable 플래그에 따라 동작하는 것이다.
configurable
설정 가능성.
true일 시 프로퍼티 삭제 / 플래그 수정이 가능하며, false일 때는 프로퍼티 삭제 플래그 수정이 불가능하다.
configurable 플래그는 강력한 제약을 주므로, 더 주의해야 한다.
한 번 configurable 플래그를 false로 설정하면 플래그 자신에 의해 다시 되돌릴 수 없다.
const obj = {};
Object.defineProperty(obj, 'age', {value:23});
Object.getOwnPropertyDescriptor(obj, 'age');
obj.age = 150;
obj;
delete obj.age;
obj;
Object.defineProperty(obj, 'age', {configurable:true});
configurable 옵션에 의해 해당 프로퍼티에 대한 삭제가 불가능하며,
프로퍼티의 플래그들도 모두 변경이 불가능하다.
정확히 말하면, 플래그 변경의 제약사항은
writable : true -> false 가능
enumerable : 변경 불가
configurable : 변경 불가 이다.
또한 추가적으로 getter/setter의 변경이 불가능하며, 새롭게 생성하는 것은 가능하다고 한다.
※ 코드에서 obj.age = 150; 동작이 수행되지 않는 것은, configurable 때문이 아니라 writable이 false이기 때문이다.
configurable은 프로퍼티 삭제 / 플래그 변경과 관련되어 있음에 유의하자.
※ 여러 프로퍼티를 동시에 생성 / 변경하거나, 설명자를 조회하고 싶다면
각 메서드 뒷부분이 복수형이면 된다.
Object.definedproperties, Object.getOwnPropertyDescriptors
사용법은 거의 유사하니 생략하겠다.
개별 프로퍼티가 아닌 객체 자체 수정을 막고 싶을 수도 있다.
Object.pretentExtension, Object.seal, Object.freeze 메서드도 있다.
pretentExtension, seal 메서드는 사용한 적이 없으나 freeze는 사용해봐서 예시를 적어보면,
바닐라 자바스크립트 프로젝트에서 자바의 enum가 필요했을 때가 있었다.
타입스크립트에서는 enum 형을 제공하지만 바닐라 자바스크립트에서는 enum은 존재하지 않았었고,
Object.freeze를 이용해 enum형과 비슷한 구조를 만들어 사용했던 기억이 있다.
자바스크립트에서 enum과 유사한 자료형을 만들기 위해서 어떻게 Object.freeze를 사용하는지는,
다음의 포스팅을 참고하길 바란다.
객체의 프로퍼티에 저장된 값이 객체, 배열, 함수등의 참조값일 때는 수정이 가능하므로 유의한다.
const obj = {
name : 'Tom',
friends : ['James', 'Jane', 'Kane'],
};
Object.freeze(obj);
obj.name = 'Jacop';
console.log(obj.name);
obj.friends = [];
console.log(obj.friends);
obj.friends.push('stranger');
console.log(obj.friends);
불변객체를 만들고 싶다면?
Object.freeze와 같은 메서드로는 '얕은 변경'을 방지할 수 있다.
그러나, 위의 예시와 같이 객체가 프로퍼티 값으로 객체를 가지고 있을 때, 중첩 객체까지의 변경을 방지할 수 없다.
그럴 때는 객체의 중첩 객체까지 재귀적으로 Object.freeze 메서드를 호출하면 된다.
예를 들면 아래와 같이 코드를 작성하면 되겠다.
function deepFreeze(obj){
if(obj && typeof obj === 'object' && !Object.isFrozen(obj)){
Object.freeze(obj);
Object.keys(obj).forEach(key => deepFreeze(obj[key]));
}
return obj;
}
const obj = {
name : 'Tom',
address : {
city : 'Seoul',
street : 'Dasanro'
}
};
deepFreeze(obj);
console.log(Object.isFrozen(obj)); // true
console.log(Object.isFrozen(obj.address)); // true
'JavaScript > theory' 카테고리의 다른 글
자바스크립트 클래스란? (0) | 2022.01.18 |
---|---|
객체 프로퍼티의 getter와 setter (0) | 2022.01.17 |
래퍼 객체(Wrapper Object)란? (0) | 2022.01.14 |
[자바스크립트] V8 엔진의 메모리 관리 이해하기 (0) | 2022.01.12 |
[자바스크립트] 일반 객체 다루기 (0) | 2022.01.11 |