값을 저장하기 위한 프로퍼티이다. 우리가 흔히 사용하는 프로퍼티이다.
함수가 본질인 접근자 프로퍼티로, 프로퍼티를 읽거나 쓸때 호출하는 함수를 값 대신에 지정할 수있는 프로퍼티이다. 이함수는 값을 획득(get)하고 설정(set)하는 역할을 담당한다.
접근자란 객체 지향 프로그래밍에서 객체가 가진 프로퍼티 값을 객체 밖에서 읽거나 쓸수 있도록 제공하는 메소드이다. 접근자 프로퍼티는 getter(획득자) 와 setter(설정자) 메서드로 표현이 된다. 객체 리터럴 안에서는 getter와 setter메소드는 get 과 set으로 나타낼수있다.
let obj = {
get propName(){
//getter, obj.propName을 실행할 때 실행되는 코드
},
set propName(value){
//setter, obj.propName = value를 실행할 때 실행되는 코드
}
}
getter 메소드는 obj.propName을 사용해 프로퍼티를 읽으려고 할때 실행되어지고, setter 메소드는 obj.propName = value로 프로퍼티에 값을 할당하려 할때 실행된다.
예제
프로퍼티 name과 surname이 있는 객체 user를 만들어보자.
let user = {
name : 'Jin',
surname : 'Park',
};
이 객체에 fullName이라는 프로퍼티를 추가해 fullName이 'Jin Park'이 되도록 할려고한다. 기존 값을 복사 & 붙여넣기를 하지않고 접근자 프로퍼티를 구현하면된다.
let user = {
name : 'Jin'
surname : 'Park',
get fullName(){
return `${this.name} ${this.surname}`
}
};
alert(user.fullName); //Jin Park
보기와 같이 바깥 코드에선 접근자 프로퍼티를 일반 프로퍼티처럼 사용이 가능하다. 접근자 프로퍼티를 사용하면 함수처럼 호출하지않고, 일반 프로퍼티에서 값에 접근하는것처럼 user.fullName
을 사용해 프로퍼티 값을 얻을수 있다.
let user = {
...
get fullName() {
return ...
}
}
user.fullName = 'Hello'; // Error
그러나 현재 getter 메소드만 존재하여, user.fullName=
을 사용하여 값을 할당하려고 하면 에러가 발생하게된다.
let user = {
...
get fullName() {
return ...
},
set fullName(value){
[this.name, this.surname] = value.split('');
}
}
user.fullName = 'Nice Weather'
alert(user.name); //Nice
alert(user.surname); //Weather
이렇게 getter와 setter 메소드를 구현하면 객체에 fullName 이라는 가상의 프로퍼티가 생긴다. 가상의 프로퍼티는 읽고 쓸순 있지만 실제로는 존재하지 않는다.
우선은 설명자가 무엇인지 부터 알아보자.
접근자 프로퍼티의 설명자란?
설명자를 설명하기전에, 프로퍼티는 key 와 value로만 이루어진것이아니다.
객체는 key(키) 와 value(값) 으로 이루어진 프로퍼티이다. 그리고 value와 함께 flag(플래그)라는 속성을 가지고도있다. flag의 속성값은 모두 true&false 의 값을 갖는다.
객체를 생서시 3가지의 플래그는 모두 기본값으로 true의 값을 가지게된다.
자바스크립트에서는 이러한 속성의 세부적인 성질을 직접 설정하거나 조회할 수 있는 방법을 제공하는데, 이 때 이용되는 특수한 객체가 바로 속성 설명자(PropertyDescriptor) 입니다.
따라서 설명자는 이러한 플래그들을 호출할수 있는데, Object.getOwnPropertyDescriptor 메소드를 사용하면된다.
let user = {
name : 'Jin'
}
let descriptor = Object.getOwnpropertyDescriptor(user, 'name');
alert(descriptor) // {value:'Jin', writable:true, enumerable:true, configurable:true}
플래그 수정
플래그를 수정할수있따. Object.defineProperty 메소드를 사용하면 된다.
Object.definProperty(user, 'name', {
writable : false
})
descriptor = Object.getOwnpropertyDescriptor(user, 'name');
alert(descriptor) // {value:'Jin', writable:false, enumerable:true, configurable:true}
프로퍼티 추가
Object.definProperty(user, 'surname', {
value : 'Park'
})
alert(user) // {name: 'Jin', surname: 'Park'}
위의 예와 같이 Object.definProperty는 인자로 준 프로퍼티가 없는 프로퍼티면 넘겨받은 정보를 이용하여 새로운 프로퍼티를 생성한다. 그러나 재미있는 점은 콘솔에 surname을 찍어보면,
descriptor = Object.getOwnpropertyDescriptor(user, 'surname');
console.log(descriptor); // {value:'Park', writable:false, enumerable:false, configurable:false}
이처럼 3번째 인자로 값을 정해주지 않는다면, 설명자 객체안의 플래그 값들은 기본값으로 false를 갖게된다.
일반적인 방법 객체생성 과 Object.defineProperty 생성 차이점
플래그들의 기본값이 true냐 false냐 이다.
다시 접근자 프로퍼티 설명자로 돌아와서, 데이터 프로퍼티의 설명자와 접근자 프로퍼티의 설명자가 다르다. 접근자 프로퍼티엔 설명자 value
와 writable
가 없는 대신에 get
과 set
이라는 함수가 있다.
접근자 프로퍼티의 설명자
아래와 같이 defineProperty에 설명자 get과 set을 전달하면 fullName을 위한 접근자를 만들수 있다.
let user = {
name : 'Jin',
surname : 'Park'
};
Object.defineProperty(user, 'fullName', {
get(){
return `${this.name} ${this.surname}`
},
set(value){
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // Jin Park
for(let key in user) alert(key); // name, surname
프로퍼티는 접근자 프로퍼티(get&set 메소드를 가짐)이나 데이터 프로퍼티(value를 가짐) 중 한 종류에만 속하고 둘 다에 속할 수 없다는 점을 항상 유의하시기 바랍니다.
따라서 한 프로퍼티에 get과 value를 동시에 설정하면 에러가 발생하게 된다.
// Error
Object.defineProperty({}, 'prop',{
get(){
return 1
},
value: 2
});
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 = 'Peter';
alert(user.name); // Peter
user.name = ''; // 입력된 값이 짧습니다. 네 글자 이상으로 구성된 이름을 입력해주세요.
user의 이름은 name에 저장되고, 프로퍼티에 접근하는 것은 getter와 setter를 통해 이뤄진다.
기술적으로는 외부 코드에서 user.name을 사용해 이름에 바로 접근할수있다. 그러나 밑줄 __
을 시작하는 프로퍼티는 객체 내부에서만 활용하고, 외부에서는 건드리지 않는것이 관습이다.
접근자 프로퍼티는 언제 어느떄나 getter와 setter를 사용해 데이터 프로퍼티의 행동과 값을 원하는 대로 조정 할수 있게 해준다는 점에서 유용하다. 데이터 프로퍼티 name과 age를 사용해서 사용자를 나타내는 객체를 구현한다고 가정해보자.
function User(name, age){
this.name = name;
this.age = age;
}
let john = new User('John', 25);
alert(john.age); // 25
그런데 곧 요구사항이 바뀌어서 age
대신 birthday
를 저장해야한다고 가정하자.
function User(name, birthday){
this.name = name;
this.birthday = birthday;
}
let john = new User('john', new Date(1992, 6, 1));
이렇게 생성자 함수를 수정하면 기존 코드 중 프로퍼티 age
를 사용하고 있는 코드도 수정해줘야 한다.
aget가 사용되는 부분을 모두 찾아서 수정하는 것도 가능하지만, 시간이 오래걸린다. 게다가 여러사람이 age를 사용하고 있따면 모두 찾아 수정하는 것 자체가 힘들다. 게다가 age
는 user
안에 있어도 나쁠것이 없는 프로퍼티이므로 기존 코드를 그대로 두고 진행해보자.
대신에 age
를 위한 getter를 추가해서 문제를 해결해보자.
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);
alert(john,age);
이처럼 둘다 사용이 가능할수있는 유용한 생성자 함수가 되었다.