LHJ

I'm a FE developer.

21.1 객체 프로퍼티 설정과 프락시

05 Jun 2020 » js_lj

21.1 객체 프로퍼티 설정과 프락시

객체 프로퍼티에는 데이터 프로퍼티접근자(accessor) 프로퍼티 두 가지가 있습니다.
우리는 두 가지 프로퍼티를 이미 살펴봤지만, 접근자 프로퍼티는 ES6의 단축 문법 때문에 잘 드러나지는 않았습니다.

함수 프로퍼티(메서드)에 대해서는 이미 잘 알고 있습니다.
접근자 프로퍼티는 메서드와 비슷한데, gettersetter 두 가지 함수로 구성된다는 것과 접근했을 때 함수라기보다는 데이터 프로퍼티와 비슷하게 동작한다는 점이 조금 다릅니다.
이런 면에서 접근자 프로퍼티를 동적 프로퍼티라고 부르기도 합니다.

동적 프로퍼티에 대해 생각해 보겠습니다.
User 클래스가 있고 이 클래스에 setEmail과 getEmail 메서드가 있습니다.
email 프로퍼티를 두면 사용자가 잘못된 이메일 주소를 설정하는 일을 막기 힘드니까 getter와 setter 메서드를 만들기로 했습니다.
클래스는 단순하게 만들겠습니다.
@ 기호가 들어있는 문자열은 모두 유효한 이메일 주소라고 간주합니다.

const USER_EMAIL = Symbol();
class User {
    setEmail(value) {
        if (!/@/.test(value)) throw new Error(`invalid email: ${value}`);
        this[USER_EMAIL] = value;
    }
    getEmail() {
        return this[USER_EMAIL];
    }
}

이 예제에서 USER_EMAIL 프로퍼티 대신 두 가지 메서드를 쓴 이유는 잘못된 이메일 주소가 저장되는 것을 막으려는 것입니다.
프로퍼티에는 심볼을 써서 실수로 직접 접근하는 일을 막았습니다.
email이나 _email 같은 문자열 프로퍼티를 썼다면 직접 접근하는 일을 막을 수 없습니다.

이런 패턴은 널리 쓰이고 잘 동작하긴 하지만 조금 불편하긴 합니다.
이 클래스는 다음과 같은 방식으로 사용해야 합니다.

const USER_EMAIL = Symbol();
class User {
    setEmail(value) {
        if (!/@/.test(value)) throw new Error(`invalid email: ${value}`);
        this[USER_EMAIL] = value;
    }
    getEmail() {
        return this[USER_EMAIL];
    }
}

const u = new User();
u.setEmail("john@doe.com");
console.log(`User email: ${u.getEmail()}`);

하지만 우리는 다음과 같은 방식을 더 자연스럽게 느낍니다.

const u = new User();
u.email = "john@doe.com";
console.log(`User email : ${u.email}`);

접근자 프로퍼티를 사용하면 후자의 자연스러운 문법을 사용하면서도, 부주의한 접근을 차단하는 전자의 장점을 누릴 수 있습니다.
접근자 프로퍼티를 사용하도록 클래스를 다시 만들어 봅시다.

const USER_EMAIL = Symbol();
class User {
    set email(value) {
        if (!/@/.test(value)) throw new Error(`invalid email: ${value}`);
        this[USER_EMAIL] = value;
    },
    get email() {
        return this[USER_EMAIL];
    }
}

함수 두 개를 사용했지만 두 함수는 email 프로퍼티 하나에 묶였습니다.
프로퍼티 할당할 때는 setter가 호출되고, 할당하는 값이 첫 번째 매개변수로 전달됩니다.
프로퍼티를 평가할 때는 getter가 호출됩니다.

setter 없이 getter만 만들 수도 있습니다.
예를 들어 사각형의 둘레(perimeter)를 얻는 getter는 다음과 같이 만들 겁니다.

class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }
    get perimeter() {
        return this.width * 2 + this.height * 2;
    }
}

사각형이 만들어질 때 이미 너비와 높이는 결정되는 것이므로, 둘레는 읽기 전용 프로퍼티로 생각하는 것이 상식적입니다.

반대로 getter 없이 setter만 만들 수도 있지만, 거의 사용하지 않습니다.