-
프로퍼티 getter와 setterfront-end 2022. 3. 2. 14:45728x90
객체의 프로퍼티는 두 종류로 나뉜다.
첫 번째 종류는 데이터 프로퍼티(data property)이며, 지금까지 사용한 모든 프로퍼티는 데이터 프로퍼티이다.
두 번째 종류는 접근자 프로퍼티(accessor property)라 불리는 새로운 종류의 프로퍼티이다.
접근자 프로퍼티의 본질은 함수인데, 이 함수의 값을 획득(get)하고 설정(set)하는 역할을 담당한다.
그런데 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 보인다.
getter와 setter
접근자 프로퍼티는 '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으로 프로퍼티에 값을 할당하려 할 때 실행된다.
let user = { name: "John", surname: "Smith" };
이 객체에 fullName이라는 프로퍼티를 추가해 fullName이 'John Smith'가 되도록 해보자.
기존 값을 복사-붙여넣기 하지 않고 접근자 프로퍼티를 구현하면 된다.
let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; } }; alert(user.fullName); // John Smith
바깥 코드에선 접근자 프로퍼티를 일반 프로퍼티처럼 사용할 수 있다. 접근자 프로퍼티는 이런 아이디어에서 출발했다.
접근자 프로퍼티를 사용하면 함수처럼 호출하지 않고, 일반 프로퍼티에서 값에 접근하는 것처럼 평범하게 user.fullName을 사용해 프로퍼티 값을 얻을 수 있다.
나머지 작업은 getter 메서드가 뒷단에서 처리해준다.
한편, 위 예시의 fullName은 getter 메서드만 가지고 있기 때문에 user.fullName= 을 사용해 값을 할당하려고 하면 에러가 발생한다.
let user = { get fullName() { return `...`; } }; user.fullName = "Test"; // Error (프로퍼티에 getter 메서드만 있어서 에러 발생)
user.fullName에 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가 없는 대신에 get과 set이라는 함수가 있다.
접근자 프로퍼티는 다음과 같은 설명자를 갖는다.
- get - 인수가 없는 함수로, 프로퍼티를 읽을 때 동작함
- set - 인수가 하나인 함수로, 프로퍼티에 값을 쓸 때 호출됨
- enumerable - 데이터 프로퍼티와 동일함
- configuration - 데이터 프로퍼티와 동일함
let user = { name: "John", surname: "Smith" }; Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { [this.name, this.surname] = value.split(" "); } }); alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname
프로퍼티는 접근자 프로퍼티(get/set 메서드를 가짐)나 데이터 프로퍼티(value를 가짐) 중 한 종류에만 속하고 둘 다에 속할 수 없다는 점을 유의해야한다.
한 프로퍼티에 get과 value를 동시에 설정하면 에러가 발생한다.
// Error: Invalid property descriptor. Object.defineProperty({}, 'prop', { get() { return 1 }, value: 2 });
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를 사용해 데이터 프로퍼티의 행동과 값을 원하는대로 조정할 수 있게 해준다는 점에서 유용하다.
데이터 프로퍼티 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를 사용하고 있는 코드도 수정해 줘야한다.
age가 사용되는 부분을 모두 찾아서 수정하는 것도 가능하지만, 시간이 오래걸린다. 게다가 여러 사람이 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 ); // birthday를 사용할 수 있습니다. alert( john.age ); // age 역시 사용할 수 있습니다.
728x90'front-end' 카테고리의 다른 글
재귀와 스택 (0) 2022.03.07 코딩 스타일 (0) 2022.03.04 모듈 내보내고 가져오기 (0) 2022.02.28 모듈(module) 이란? (0) 2022.02.25