
객체 프로퍼티는 값(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 지양하기