Skip to content

Latest commit

 

History

History
236 lines (170 loc) · 7.45 KB

4주차.md

File metadata and controls

236 lines (170 loc) · 7.45 KB

프로퍼티 플래그와 설명자

객체엔 프로퍼티가 저장되지만, 프로퍼티는 생각한 것 이상으로 유연하고 강력한 자료구조이다.

프로퍼티 플래그

객체 프로퍼티는 값과 함께 플래그라는 특별한 속성을 가지고 있다.

플래그의 속성은 3가지가 있는데 아래와 같다.

  • writable – true이면 값을 수정할 수 있다. 그렇지 않다면 읽기만 가능하다.
  • enumerable – true이면 반복문을 사용해 나열할 수 있다. 그렇지 않다면 반복문을 사용해 나열할 수 없다.
  • configurable – true이면 프로퍼티 삭제나 플래그 수정이 가능하다. 그렇지 않다면 프로퍼티 삭제와 플래그 수정이 불가능하다.

평범한 방식으로 프로퍼티를 생성한다면 위의 속성 모두가 true가 된다.

프로퍼티 플래그 얻기

해당 플래그는 Object.getOwnPropertyDescriptor메서드를 사용하면 특정 프로퍼티에 대한 정보를 모두 얻을 수 있다.

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를 사용하면 플래그를 변경할 수 있다. defineProperty메서드는 객체에 해당 프로퍼티가 있으면 플래그를 원하는 대로 변경해준다. 프로퍼티가 없으면 인수로 넘겨받은 정보를 이용해 새로운 프로퍼티를 만든다. 이때 플래그 정보가 없으면 플래그 값은 자동으로 false가 된다.

let user = {};

Object.defineProperty(user, "name", {
  value: "John",
});

let descriptor = Object.getOwnPropertyDescriptor(user, "name");

alert(JSON.stringify(descriptor, null, 2));
/*
{
  "value": "John",
  "writable": false,
  "enumerable": false,
  "configurable": false // 전체가 false
}
 */

각 플래그별 기능

writable 플래그

이제 defineProperty를 사용해 writable 플래그를 true로 변경하지 않는 한 그 누구도 객체의 이름을 변경할 수 없게 된다.

let user = {
  name: "John",
};

Object.defineProperty(user, "name", {
  writable: false,
});

user.name = "Pete"; // Error: Cannot assign to read only property 'name'

enumerable 플래그

객체 내장 메서드 toString은 열거가 불가능(non-enumerable)하기 때문에 for..in 사용시 나타나지 않는다.

let user = {
  name: "John",
  toString() {
    return this.name;
  },
};

//커스텀 toString은 for...in을 사용해 열거할 수 있습니다.
for (let key in user) alert(key); // name, toString

configurable 플래그

구성 가능하지 않음을 나타내는 플래그(non-configurable flag)인 configurable:false는 몇몇 내장 객체나 프로퍼티에 기본으로 설정되어있다.

어떤 프로퍼티의 configurable 플래그가 false로 설정되어 있다면 해당 프로퍼티는 객체에서 지울 수 없다.

let descriptor = Object.getOwnPropertyDescriptor(Math, "PI");

alert(JSON.stringify(descriptor, null, 2));
/*
{
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
}
*/
//내장 객체 Math의 PI 프로퍼티가 대표적인 예입니다. 이 프로퍼티는 쓰기와 열거, 구성이 불가능!!
Math.PI = 3; // Error

객체 수정을 막아주는 다양한 메서드

  • Object.seal(obj) 새로운 프로퍼티 추가나 기존 프로퍼티 삭제를 막아준다. 프로퍼티 전체에 configurable: false를 설정하는 것과 동일한 효과이다.

  • Object.freeze(obj) 새로운 프로퍼티 추가나 기존 프로퍼티 삭제, 수정을 막아줍니다. 프로퍼티 전체에 configurable: false, writable: false를 설정하는 것과 동일한 효과가 있다.

프로퍼티 getter와 setter

객체의 프로퍼티는 두 종류로 나뉩니다.

첫 번째 종류는 데이터 프로퍼티(data property)로, 지금까지 사용한 모든 프로퍼티는 데이터 프로퍼티이다.

두 번째는 접근자 프로퍼티(accessor property) 라 불리는 새로운 종류의 프로퍼티입니다. 접근자 프로퍼티의 본질은 함수인데, 이 함수는 값을 획득(get)하고 설정(set)하는 역할을 담당한다. 그런데 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 보이게 된다.

getter와 setter

접근자 프로퍼티는 'getter(획득자)'‘setter(설정자)’메서드로 표현됩니다. 객체 리터럴 안에서 gettersetter 메서드는 getset으로 나타낼 수 있다.

getter 메서드는 obj.propName을 사용해 프로퍼티를 읽으려고 할 때 실행되고, setter 메서드는 obj.propName = value으로 프로퍼티에 값을 할당하려 할 때 실행된다.

let obj = {
  get propName() {
    // getter, obj.propName을 실행할 때 실행되는 코드
  },

  set propName(value) {
    // 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";

접근자 프로퍼티 설명자

접근자 프로퍼티엔 설명자 value와 writable가 없는 대신에 get과 set이라는 함수가 있습니다.

따라서 접근자 프로퍼티는 다음과 같은 설명자를 갖습니다.

  • get – 인수가 없는 함수로, 프로퍼티를 읽을 때 동작함
  • set – 인수가 하나인 함수로, 프로퍼티에 값을 쓸 때 호출됨
  • enumerable – 데이터 프로퍼티와 동일함
  • configurable – 데이터 프로퍼티와 동일함

defineProperty에 설명자 get과 set을 전달하면 fullName을 위한 접근자를 만들 수 있다.

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

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 = ""; // 너무 짧은 이름을 할당하려 함

위와 같이 getter와 setter를 분리한다는 것은, 데이터를 얻어야하는 경우와 수정하는 경우를 나누어서 로직을 활용한다는 것입니다. 그렇기에 데이터의 접근은 오직 getter, 수정은 setter를 통한 분할을 통해 데이터의 안정성을 높힌다는 사실을 알고 있어야합니다.