객체엔 프로퍼티가 저장된다.
지금까진 프로퍼티를 단순히 '키-값' 쌍의 관점에서만 다뤘다.
그런데 사실 프로퍼티는 우리가 생각했던 것보다 더 유연하고 강력한 자료구조이다.
객체 프로퍼티 추가 구성 옵션 몇 가지를 다루고,
이어지는 챕터에선 이 옵션들을 이용해 손십게 getter나 setter함수를 만드는 법을 알아보자.
객체 프로퍼티는 값(value)와 함께 플래그(flag)라 불리는 특별한 속성 세 가지를 갖는다.
writable : true 이면 값을 수정할 수 있다. 그렇지 않다면 읽기만 가능하다.enumerable : 'true` 이면 반복문을 사용해 나열할 수 있다. 그렇지 않으면 반복문을 사용해 나열할 수 없다.configurable : true 이면 프로퍼티 삭제나 플래그 수정이 가능하다. 그렇지 않다면 프로퍼티 삭제와 플래그 수정이 불가능하다.프로퍼티 플래그는 특별한 경우가 아니고선 다룰 일이 없다.
지금까지 해왔던 평범한 방식으로 프로퍼티를 만들면 해당 프로퍼티의 플래그는 모두 true가 된다.
이렇게 true로 설정된 플래그는 언제든 수정할 수 있다.
Object.getOwnPropertyDescriptor메서드를 사용하면 특정 프로퍼티에 대한 정보를 모두 얻을 수 있다.
let descriptor = Object.getOwnPropertyDescripter(obj, propertyName);
메서드를 호출하면 "프로퍼티 설명자(descriptor)"라고 불리는 객체가 반환되는데,
여기에는 프로퍼티 값과 세 플래그에 대한 정보가 모두 담겨있다.
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* property descriptor:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
메서드 Object.defineProperty를 사용하면 플래그를 변경할 수 있다.
Object.defineProperty(obj, propertyName, descriptor)
descriptor: 적용하고자 하는 프로퍼티 설명자
defineProperty메서드는 객체에 해당 프로퍼티가 있으면 플래그를 원하는 대로 변경해준다.
프로퍼티가 없으면 인수로 넘겨받은 정보를 이용해 새로운 프로퍼티를 만든다.
이때 정보가 없으면 플래그 값을 자동으로 false가 된다.
아래 예시를 보면 프로퍼티 name이 새로 만들어지고, 모든 플래그 값이 false가 된 것을 확인할 수 있다.
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
평범한 방식으로 객체 프로퍼티 user.name을 만들었을 때와
defineProperty 를 이용해 프로퍼티를 만들었을 때의 가장 큰 차이점은 플래그에 있다.
defineProperty를 사용해 프로퍼티를 만든 경우, descriptor에 플래그 값을 명시하지 않으면 플래그 값이 자동으로
false가 된다.
플래그 값을 true를 설정하려면 descriptor에 true라고 명시해 주어야한다.
writable 플래그
writable 플래그를 사용해 user.name에 값을 쓰지 못하도록 해보자.
let user = {
name: "Jonh"
};
Object.defineProperty(user,"name", {writable: false});
user.name = "pete"; // Error: Cannot assign to read only property 'name'
이제 defineProperty를 사용해 writable플래그를 true로 변경하지 않는 한
그 누구도 객체의 이름을 변경할 수 없게 되었다.
비 엄격 모드에선 읽기 전용 프로퍼티에 값을 쓰려고 해도 에러가 발생하지 않는다.
다만 이때 값을 변경하는 것은 불가능하다.
비 엄격 모드에선 이와 같이 플래그에서 정한 규칙을 위반하는 행위는 에러 없이 그냥 무시된다.
user에 커스텀 메서드 toString을 추가해보자
객체 내장 메서드 toString은 열거가 불가능하기 때문에
for..in 사용시 나타나지 않는다.
하지만 커스텀 toString을 추가하면 아래와 같이 for..in에 toString이 나타닌다.
그런데 특정 프로퍼티의 enumerable 플래그 값을 false로 설정하면 for..in 반복문에 나타나지 않게 할 수 있다.
커스텀 toString도 열거가 불가능하게 할 수 있다.
let user = {
name: "John",
toString(){
return this.name
}
}
Object.defineProperty(user,"toString",{
enumerable: false
});
// 이제 for..in을 사용해 toString을 열거할 수 없음
for(let key in user) alert(key); // name
열거가 불가능한 프로퍼티는 Object.keys에도 배제된다.
구성 가능하지 않음을 나타내는 플래그인 configurable:false는 몇몇 내장 객체나 프로퍼티에 기본으로 설정되어 있다.
어떤 프로퍼티의 configurable플래그가 false로 설정되어 있다면 해당 프로퍼티는 객체에서 지울 수 없다.
내장 객체 Math의 PI 프로퍼티가 태표적인 예이다.
이 프로퍼티는 쓰기와 열거, 구성이 불가능하다.
개발자가 코드를 사용해 Math.PI의 값을 변경하거나 덮어쓰는 것도 불가능하다.
물론 지우는 것도 불가능하다.
configurable플래그를 false로 설정하면 돌이킬 방법이 없다.
defineProperty를 써도 값을 true로 되돌릴 수 없다.
configurable: false가 만들어내는 구체적인 제약 사항은 아래와 같다.
1. configurable플래그를 수정할 수 없음
2. enumerable 플래그를 수정할 수 없음
3. writable: false의 값을 ture로 바꿀 수 없음 (true를 false로 변경은 가능)
4. 접근자 프로퍼티 get/set을 변경할 수 없음(새롭게 만드는 것은 가능)
이러한 특징을 이용하면 아래와 같이 영원히 변경할 수 없는 프로퍼티(user.name)을 만들 수 있다.
let user = {};
Object.defineProperty(user, "name",{
value: "john",
writable: false,
configurable: false
})
// user.name프로퍼티의 값이나 플래그를 변경할 수 없다.
// 아래와 같이 변경하려고 하면 에러가 난다.
// user.name = "Pete" // Error
// delete user.name // Error
Object.defineProperty(user, "name", {writable: true}) // Error
configurable플래그가 false이더라도 writeable플래그가 true이면 프로퍼티 값을 변경할 수 있다.
configurable: false는 플래그 값 변경이나 프로퍼티 삭제를 막기 위해 만들어졌지,
프로퍼티 값 변경을 막기 위해 만들어진 게 아니다.
Object.defineProperties(obj, description)메서드를 사용하면
프로퍼티 여러 개를 한 번에 정의할 수 있다.
Object.defineProperties (obj, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
})
Object.getOwnPropertyDescriptors(obj)메서드를 사용하면
프로퍼티 설명자를 전부 한꺼번에 가져올 수 있다.
이 메서드를 Object.defineProperties와 함께 사용하면 객체 복사 시 플래그도 함께 복사할 수 있다.
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
지금까진 아래처럼 할당 연사자를 사용해 프로퍼티를 복사하는 방법으로 객체를 복사해왔다.
for (let key in user) {
clone[key] = user[key]
}
그런데 이 방법은 플래그는 복사하지 않는다.
플래그 정보도 복사하려면 Object.defineProperties를 사용하기 바란다.
위 샘플 코드처럼 for..in을 사용해 객체를 복사하면 심볼형 프로퍼티도 놓치게 된다.
하지만 Object.getOwnPropertyDescriptors는 심볼형 프로퍼티를 포함한 프로퍼티 설명자 전체를 반환한다.
프로퍼티 설명자는 특정 프로퍼티 하나를 대상으로 한다.
아래 메서드를 사용하면 한 객체 내 프로퍼티 전체를 대상으로 하는 제약사항을 만들 수 있다.
객체에 새로운 프로퍼티를 추가할 수 없게 한다.
새로운 프로퍼티 추가나 기존 프로퍼티 삭제를 막아준다.
프로퍼티 전체에 configurable: false를 설정하는 것과 동일한 효과이다.
새로운 프로퍼티 추가나 기존 프로퍼티 삭제, 수정을 막아준다.
프로퍼티 전체에 configurable: false, writable: false를 설정하는 것과 동일한 효과이다.
아래 메서드는 위 세 가지 메서드를 사용해서 설정한 제약사항을 확인할 때 사용할 수 있다.
새로운 프로퍼티 추가하는게 불가능한 경우 false를 그렇지 않은 경우 true를 반환
프로퍼티 추가, 삭제가 불가능하고 모든 프로퍼티가 configurable: false이면 true를 반환
프로퍼티 추가, 삭제 변경이 불가능하고 모든 프로퍼티가 configurable: false, writable: false 이면 true를 반환