7. 객체 프로퍼티 설정
7.1 프로퍼티 플래그와 설명자
프로퍼티 플래그
-
객체 프로퍼티는 값(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를 사용하면 플래그를 변경할 수 있습니다.
Object.defineProperty(obj, propertyName, descriptor)
obj, propertyName
: 설명자를 적용하고 싶은 객체와 객체 프로퍼티descriptor
: 적용하고자 하는 프로퍼티 설명자- defineProperty메서드는 객체에 해당 프로퍼티가 있으면 플래그를 원하는 대로 변경해줍니다. 프로퍼티가 없으면 인수로 넘겨받은 정보를 이용해 새로운 프로퍼티를 만듭니다. 이때 플래그 정보가 없으면 플래그 값은 자동으로 false가 됩니다.
writable 플래그
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'
- 이제 defineProperty를 사용해 writable 플래그를 true로 변경하지 않는 한 그 누구도 객체의 이름을 변경할 수 없게 되었습니다.
enumerable 플래그
- 객체 내장 메서드
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
- 열거가 불가능한 프로퍼티는 Object.keys에도 배제됩니다.
alert(Object.keys(user)); // name
configurable 플래그
-
어떤 프로퍼티의
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
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
Object.getOwnPropertyDescriptors(obj)
메서드를 사용하면 프로퍼티 설명자를 전부 한꺼번에 가져올 수 있습니다.
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
- 할당 연산자를 사용해 프로퍼티를 복사하면 플래그정보, 심볼형 프로퍼티를 놓칠 수 있습니다. 하지만
Object.getOwnPropertyDescriptors
는 심볼형 프로퍼티를 포함한 프로퍼티 설명자 전체를 반환합니다.
객체 수정을 막아주는 다양한 메서드(생략)
7.2 프로퍼티 getter와 setter
- 객체의 프로퍼티는 두 종류로 나뉩니다.
- 첫 번째 종류는 데이터 프로퍼티(data property) 입니다. 지금까지 사용한 모든 프로퍼티는 데이터 프로퍼티입니다. 데이터 프로퍼티 조작 방법에 대해선 모두 알고 계실 것이라 생각합니다.
- 두 번째는 접근자 프로퍼티(accessor property) 라 불리는 새로운 종류의 프로퍼티입니다. 접근자 프로퍼티의 본질은 함수인데, 이 함수는 값을 획득(get)하고 설정(set)하는 역할을 담당합니다. 그런데 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 보입니다.
getter와 setter
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
– 데이터 프로퍼티와 동일함 - 프로퍼티는 접근자 프로퍼티(get/set 메서드를 가짐)나 데이터 프로퍼티(value를 가짐) 중 한 종류에만 속하고 둘 다에 속할 수 없다는 점을 항상 유의하시기 바랍니다.
- 한 프로퍼티에 get과 value를 동시에 설정하면 에러가 발생합니다
getter와 setter 똑똑하게 활용하기
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 지양하기
📚 참고 : javascript.info
Author And Source
이 문제에 관하여(7. 객체 프로퍼티 설정), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@protect-me/7.-객체-프로퍼티-설정저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)