객체 프로퍼티는 값(value) 과 함께 플래그(flag)라 불리는 특별한 속성 세 가지를 갖습니다.
- writable
– true
이면 값을 수정할 수 있습니다. 그렇지 않다면 읽기만 가능합니다.
- enumerable
– true
이면 반복문을 사용해 나열할 수 있습니다. 그렇지 않다면 반복문을 사용해 나열할 수 없습니다.
- configurable
– true
이면 프로퍼티 삭제나 플래그 수정이 가능합니다. 그렇지 않다면 프로퍼티 삭제와 플래그 수정이 불가능합니다.
지금까지 해왔던 '평범한 방식’으로 프로퍼티를 만들면 해당 프로퍼티의 플래그는 모두 true
가 됩니다. 이렇게 true
로 설정된 플래그는 언제든 수정할 수 있습니다.
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
: 정보를 얻고자 하는 객체propertyName
: 정보를 얻고자 하는 객체 내 프로퍼티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(obj, propertyName, descriptor)
obj, propertyName
: 설명자를 적용하고 싶은 객체와 객체 프로퍼티descriptor
: 적용하고자 하는 프로퍼티 설명자writable
플래그를 사용해 user.name
에 값을 쓰지 못하도록(non-writable) 해봅시다.let user = {
name: "John"
};
Object.defineProperty(user, "name", {
writable: false
});
user.name = "Pete"; // Error: Cannot assign to read only property 'name'
toString
은 열거가 불가능(non-enumerable)하기 때문에 for..in
사용시 나타나지 않습니다. 하지만 커스텀 toString
을 추가하면 아래와 같이 for..in
에 toString
이 나타납니다.let user = {
name: "John",
toString() {
return this.name;
}
};
//커스텀 toString은 for...in을 사용해 열거할 수 있습니다.
for (let key in user) alert(key); // name, 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
alert(Object.keys(user)); // name
어떤 프로퍼티의 configurable
플래그가 false
로 설정되어 있다면 해당 프로퍼티는 객체에서 지우거나 변경하거나 덮어쓸 수 없습니다.
configurable
플래그를 false
로 설정하면 돌이킬 방법이 없습니다. defineProperty
를 써도 값을 true
로 되돌릴 수 없죠.
configurable:false
가 만들어내는 구체적인 제약사항은 아래와 같습니다.
- configurable
플래그를 수정할 수 없음
- enumerable
플래그를 수정할 수 없음.
- writable: false
의 값을 true
로 바꿀 수 없음(true
를 false
로 변경하는 것은 가능함).
- 접근자 프로퍼티 get/set
을 변경할 수 없음(새롭게 만드는 것은 가능함).
user.name
) 만들기let user = { };
Object.defineProperty(user, "name", {
value: "John",
writable: false,
configurable: false
});
// user.name 프로퍼티의 값이나 플래그를 변경할 수 없습니다.
// 아래와 같이 변경하려고 하면 에러가 발생합니다.
// user.name = "Pete"
// delete user.name
// Object.defineProperty(user, "name", { value: "Pete" })
Object.defineProperty(user, "name", {writable: true}); // Error
"non-configurable"은 "non-writable"과 다릅니다.
configurable
플래그가false
이더라도writable
플래그가true
이면 프로퍼티 값을 변경할 수 있습니다.
configurable: false
는 플래그 값 변경이나 프로퍼티 삭제를 막기 위해 만들어졌지, 프로퍼티 값 변경을 막기 위해 만들어진 게 아닙니다.
Object.defineProperties(obj, descriptors)
메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있습니다.Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
// ex)
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
Object.getOwnPropertyDescriptors(obj)
메서드를 사용하면 프로퍼티 설명자를 전부 한꺼번에 가져올 수 있습니다.let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Object.getOwnPropertyDescriptors
는 심볼형 프로퍼티를 포함한 프로퍼티 설명자 전체를 반환합니다.let obj = {
get propName() {
// getter, obj.propName을 실행할 때 실행되는 코드
},
set propName(value) {
// setter, obj.propName = value를 실행할 때 실행되는 코드
}
};
getter
메서드는 obj.propName
을 사용해 프로퍼티를 읽으려고 할 때 실행되고, setter
메서드는 obj.propName = value
으로 프로퍼티에 값을 할당하려 할 때 실행됩니다.let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// 주어진 값을 사용해 set fullName이 실행됩니다.
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
user.fullName
을 사용해 프로퍼티 값을 얻을 수 있습니다. 나머지 작업은 getter
메서드가 뒷단에서 처리해줍니다.getter
와 setter
메서드를 구현하면 객체엔 fullName
이라는 '가상’의 프로퍼티가 생깁니다. 가상의 프로퍼티는 읽고 쓸 순 있지만 실제로는 존재하지 않습니다.value
와 writable
가 없는 대신에 get
과 set
이라는 함수가 있습니다.get
– 인수가 없는 함수로, 프로퍼티를 읽을 때 동작함set
– 인수가 하나인 함수로, 프로퍼티에 값을 쓸 때 호출됨enumerable
– 데이터 프로퍼티와 동일함configurable
– 데이터 프로퍼티와 동일함getter
와 setter
를 ‘실제’ 프로퍼티 값을 감싸는 래퍼(wrapper)처럼 사용하면, 프로퍼티 값을 원하는 대로 통제할 수 있습니다.name
을 위한 setter
를 만들어 user
의 이름이 너무 짧아지는 걸 방지하고 있습니다. 실제 값은 별도의 프로퍼티 _name
에 저장됩니다.let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("입력하신 값이 너무 짧습니다. 네 글자 이상으로 구성된 이름을 입력하세요.");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // 너무 짧은 이름을 할당하려 함
user
의 이름은 _name
에 저장되고, 프로퍼티에 접근하는 것은 getter
와 setter
를 통해 이뤄집니다.
기술적으론 외부 코드에서 user._name
을 사용해 이름에 바로 접근할 수 있습니다. 그러나 밑줄 "_" 로 시작하는 프로퍼티는 객체 내부에서만 활용하고, 외부에서는 건드리지 않는 것이 관습입니다.
getter
와 setter
를 사용해 데이터 프로퍼티의 행동과 값을 원하는 대로 조정할 수 있게 해준다는 점에서 유용합니다.function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// age는 현재 날짜와 생일을 기준으로 계산됩니다.
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert( john.birthday ); // birthday를 사용할 수 있습니다.
alert( john.age ); // age 역시 사용할 수 있습니다.
객체의 상태가 변경되는 것은 객체 스스로의 행동에 의해서야 한다.
이렇게 설계될 때 객체는 자율적인 객체가 되고 외부의 영향을 받지 않음으로써 느슨한 결합과 유연한 협력을 이룰 수 있다.
getter, setter는 자신의 상태정보를 외부에 노출하고 이것은 외부의 영향으로 상태정보가 변할 수 있는 가능성을 열어두게된다. 한 객체의 변화가 여러 객체에게 영향을 주는 이러한 코드가 많아질수록 훗날 유지보수가 어려워질 것이다.
출처 : Getter, Setter 지양하기