객체 프로퍼티는 데이터 프로퍼티, 접근자 프로퍼티로 나뉜다.
접근자 프로퍼티는 'getter(획득자)'와 ‘setter(설정자)’ 메서드로 표현될 수 있다. 그리고 객체 리터럴 안에서 getter, setter는 각각 get, set으로 나타낼 수 있다.
getter 메서드는 obj.propName을 사용해 프로퍼티를 읽으려고 할 때 실행되고, setter 메서드는 obj.propName = value으로 프로퍼티에 값을 할당하려 할 때 실행된다.
const obj = {
macro:'hi',
get hello(){
return this.macro;
},
set hello(value){
this.macro = value;
}
};
console.log(obj.hello); // hi
obj.hello = 'hello';
console.log(obj.hello); // hello
다음과 같이 프로퍼티를 호출할 때 get, 프로퍼티에 값을 할당할 때 set 메서드가 실행된다.
객체 내부에서는 get, set 키워드를 사용했지만
객체 외부에서는 일반 프로퍼티처럼 obj.hello 를 사용하면 된다.
물론 getter, setter를 모두 구현할 필요는 없이 둘 중 하나만 사용할 수도 있다.
const obj = {
macro:'hi',
get hello(){
return this.macro;
},
};
console.log(obj.hello); // hi
obj.hello = 'Test'; // Error
console.log(obj.hello); // hi
다음과 같이 코드를 작성하면, setter가 존재하지 않기 때문에 getter에서 출력된 값이 변하지 않는다.
다시 한 번 더 setter 메서드를 추가한 예시를 살펴보자.
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
이렇게 getter와 setter 메서드를 구현하면 객체엔 fullName이라는 '가상’의 프로퍼티가 생긴다. 가상의 프로퍼티는 읽고 쓸 순 있지만 실제로는 존재하지 않는다.
데이터 프로퍼티의 설명자와 접근자 프로퍼티의 설명자는 다르다.
데이터 설명자에는 value, writable, enumerable, configurable 속성이 있으며
접근자 프로퍼티에는 get, set, enumerable, configurable 속성이 있다.
각각 설명자들의 개념은 다음과 같다.
데이터 프로퍼티와 동일하게 접근자 프로퍼티에도 defineProperty를 통해 getter, setter를 생성할 수 있다.
const obj = {
macro:'hi',
};
Object.defineProperty(obj, 'hello', {
get(){
return this.macro;
},
set(value){
this.macro = value;
}
});
console.log(obj.hello); // hi
obj.hello = 'hello world!';
console.log(obj.hello); // hello world!
Object.getOwnPropertyDescriptor(obj, 'hello');
// obj.hello 프로퍼티 또한 설명자 객체를 얻을 수 있으며
// 설명자 객체는 다음의 형태를 가진다.
// configurable: false
// enumerable: false
// get: ƒ get()
// set: ƒ set(value)
프로퍼티는 접근자 프로퍼티(get/set 메서드를 가짐)나 데이터 프로퍼티(value를 가짐) 중 한 종류에만 속하고 둘 다에 속할 수 없다는 점을 항상 유의하자.
한 프로퍼티에 get과 value를 동시에 설정하면 에러가 발생한다.
getter와 setter를 ‘실제’ 프로퍼티 값을 감싸는 래퍼(wrapper)처럼 사용하면, 프로퍼티 값을 원하는 대로 통제할 수 있다.
const obj = {
get name(){
return this._name;
},
set name(value){
if(value.length < 4){
console.log('이름은 4글자 이상이어야 합니다.');
return;
}
this._name = value;
}
};
obj.name = 'TT'; // 이름은 4글자 이상이어야 합니다.
console.log(obj.name); // undefined
obj.name = 'Totoro';
console.log(obj.name); // Totoro
다음과 같이 작성하면, obj.name 프로퍼티를 호출하면 get의 로직에 따르게 되고, obj.name = value로 name 프로퍼티에 값을 할당하면 set의 로직에 따르게 된다.
set 메서드를 통해 name 프로퍼티에 4글자 미만의 이름을 할당할 수 없으므로 'TT'를 할당할 수 없고, 'Totoro'은 4글자 이상이므로 성공적으로 할당된다.
메서드 마지막에 위치한 name 프로퍼티는 protected 속성이며, 객체 내부와 그 자손에서만 사용하겠다는 의미다. 물론, 관습상 으로 시작하는 속성은 외부에서 호출하지 않는 것이지 '호출이 불가능한 속성은 아니다'.
접근자 프로퍼티는 getter와 setter를 사용해 데이터 프로퍼티의 행동과 값을 원하는 대로 조정할 수 있게 해준다는 점에서 유용하다.
개발자가 임의로 데이터 프로퍼티를 검증하는 로직을 setter에 넣어서 원하는 동작을 구현한다거나, 객체의 속성 값에 대하여 제약조건을 부여할 수도, 변경이 불가능하게 만들 수도 있다.