ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JS/Object] 자바스크립트, 객체의 프로퍼티(Property)에 대하여 (Object.defineProperty()와 getter, setter사용하기)
    Frontend 2019. 7. 31. 14:17

    Object

    자바스크립트의 Object, 즉 객체는 여러가지의 자료(Data)들과 함수(Function)들의 집합이다. 자바스크립트에서는 원시 데이터 타입인 Number, String, Boolean, Null, Undefined를 제외한 모든 것들을 다 Object type이라고 한다. 함수도, 배열도 모두 Object라는 큰 범주 안에 속한다. 

     

     

    const dessert = {
      dessertName: 'chocolate cake',
      sweetness: 6,
      ingredient: ['flour', 'eggs', 'chocolate', 'sugar', 'butter']
    };
    

     

    객체는 new Object()Object.create(), 혹은 literal notation을 사용하여 초기화될 수 있다.

    위와 같이 쓰여진 객체가 바로 literal notation으로 쓰여진 형태를 말한다.

    객체 초기자(Object initializer)는 0개 이상의 속성명과 그에 해당하는 값의 목록이 {}(중괄호)에 묶여있다.

     

     

    const party = {
      host: 'Tom',
      eventName: 'birthday party',
      time: '7pm',
      location: 'ABC restaurant',
      invited: ['Sally', 'Ann', 'Paul', 'Andrew'],
      invitation: function () {
        return `You are invited to ${this.host}'s ${this.eventName}!`;
      }
    };
    
    console.log(party.invitation());  // You are invited to Tom's birthday party!

     

    객체의 값에는 원시 데이터 타입 뿐만 아니라 배열, 객체, 함수가 들어갈 수도 있다.

    객체 내부에 함수를 쓰고 그 안에서 this를 사용한다면 this는 그 함수가 존재하는 객체 자체를 가리킨다.

     

     

    party 객체 코드를 보면 객체 안에 host, eventName 등의 값들이 저장된 것처럼 보이지만 사실은 저 property들은 실제 값이 저장된 메모리의 주소를 가리키고 있을 뿐이다. 만약 우리가 party.host를 'Jane'으로 바꾼다면 그것은 host라는 프로퍼티가 가리키고 있는 메모리 공간에서 이루어진다.

     

     

     

    Object.defineProperty()

    const dessert = {
      dessertName: 'chocolate cake',
      sweetness: 6,
      ingredient: ['flour', 'eggs', 'chocolate', 'sugar', 'butter']
    };
    
    dessert.price = '20$';

     

     

    만약에 dessert 객체에 새로운 프로퍼티를 추가하고 싶으면 위와같이 할당 연산자를 사용해서 간단하게 추가할 수 있다. 만약에 위 코드처럼 값을 할당하여 속성을 추가하는 일반적인 방법을 사용하면 자동으로 enumerable과 configurable, writable 속성이 true로 설정된다.

     

    즉, enumerable: true이기 때문에 값을 for...in문 등의 방법으로 열거할 수 있고,

    configurable: true이기 때문에 delete 등을 사용하여 속성을 삭제할 수 있고,

    writable: true이기 때문에 추후에 할당 연산자로 값을 변경할 수 있다.

     

     

    const dessert = {
      dessertName: 'chocolate cake',
      sweetness: 6,
      ingredient: ['flour', 'eggs', 'chocolate', 'sugar', 'butter']
    };
    
    Object.defineProperty(dessert, 'price', {
      value: '20$'
    });

     

     

    Object.defineProperty()는 객체에 직접 새로운 속성을 정의하거나 이미 존재하는 속성을 수정한 후 그 객체를 반환한다. defineProperty() 메소드를 사용하면 enumerable, configurable 등의 속성을 상세하게 조절할 수 있고, 값을 getter나 setter를 사용하여 다른 객체나 변수에서 읽어올 수도 있다.

     

     

     


    Object.defineProperty(obj, prop, descriptor);

    Object.defineProperty() 메소드는 3개의 인자를 받는다.

     

    • obj: 프로퍼티를 정의할 대상 객체,
    • prop: 새로 정의하거나 수정하려는 프로퍼티의 이름,
    • descriptor: 새로 정의하거나 수정하려는 속성을 쓰는 객체이다.

     

    여기서 속성 서술자(property descriptors)데이터 서술자(data descriptors)접근자 서술자(accessor descriptors)로 나뉜다. 데이터 서술자는 값을 가지는 속성이고, 접근자 서술자는 접근자(getter)와 서술자(setter) 한 쌍을 가지는 속성이다.

     

     

    * 데이터 서술자(data descriptors) 접근자 서술자(accessor descriptors)는 모두 가지는 키:

    configurable 이 속성의 값을 변경할 수 있고, 대상 객체에서 삭제할 수도 있다면 true. 기본값은 false.
    enumerable 이 속성이 대상 객체의 속성 열거 시 노출된다면 true. 기본값은 false.

     

    * 데이터 서술자(data descriptors)만 가지는 키:

    value 속성에 연관된 값. 아무 유효한 JavaScript 값(숫자, 객체, 함수 등)이나 가능.
    기본값은 undefined.
    writable 할당 연산자로 속성의 값을 바꿀 수 있다면 true.
    기본값은 false.

     

    * 접근자 서술자(accessor descriptors)만 가지는 키:

    get 속성 접근자로 사용할 함수, 접근자가 없다면 undefined. 속성에 접근하면 이 함수를 매개변수 없이 호출하고, 그 반환값이 속성의 값이 된다. 이 때 this 값은 이 속성을 가진 객체(상속으로 인해 원래 정의한 객체가 아닐 수 있음)이다.
    기본값은 undefined.
    set 속성 설정자로 사용할 함수, 설정자가 없다면 undefined. 속성에 값을 할당하면 이 함수를 하나의 매개변수(할당하려는 값)로 호출한다. 이 때 this 값은 이 속성을 가진 객체이다.
    기본값은 undefined.

     

     

     

    위 내용은 MDN에도 적혀있으므로 자세한 설명은 생략하고 예제 코드를 살펴보자.

     

    const dessert = {
      dessertName: 'chocolate cake',
      sweetness: 6,
      ingredient: ['flour', 'eggs', 'chocolate', 'sugar', 'butter']
    };
    
    Object.defineProperty(dessert, 'price', {
      value: '20$'
    });

     

    위의 price 속성은 value 속성을 가지므로 데이터 서술자(data descriptors)를 가지게된다.

     

    이 때 price 속성은 configurable과 enumerable 키가 기본값인 false로 되어있으므로 값을 삭제할 수 없고, for...in문이다 Object.keys() 등으로 접근하여 순회하는 것도 불가능하다. 또한 writable 키도 기본값 false로 되어있으므로 할당 연산자로 값을 수정할 수도 없다.

     

     

     

     

    Object.defineProperty(dessert, 'price', {
      value: '20$',
      enumerable: true,
      configurable: true,
      writable: true
    });

     

    이제 enumerable 키와 configurable, writable 키를 모두 true로 설정하였다.

    이제 할당 연산자로 값을 수정할 수도 있고 for...in문으로 해당 프로퍼티에 접근할 수도 있고

    delete로 해당 프로퍼티를 삭제할 수도 있다.

     

     

    이 것이 바로 데이터 서술자이다. 말그대로 프로퍼티가 데이터의 값을 가지고 있는 것이다.

    반면에 접근자 서술자는 접근자(getter)-서술자(setter) 함수를 실행하여 함수의 리턴 값을 가져온다.

     

     

    const dessert = {
      dessertName: 'chocolate cake',
      sweetness: 6,
      ingredient: ['flour', 'eggs', 'chocolate', 'sugar', 'butter']
    };
    
    let cakePrice = '20$';
    
    Object.defineProperty(dessert, 'price', {
      enumerable: true,
      configurable: true,
      get () {
        return cakePrice;
      },
      set (newValue) {
        cakePrice = newValue;
      }
    });

     

    위 코드를 보면 price 속성을 정의할때 get 함수를 사용하여 전역 공간에 있는 cakePrice 변수를 리턴하라고 되어있다.

    또한 set 함수를 통해 cakePrice 변수를 새로운 값으로 할당하도록 되어있다.

     

     

    위 dessert 객체를 콘솔 창에서 살펴보면 price라는 속성과 함께, price 속성에 대한 get, set 함수가 생겨난 것을 확인할 수 있다. 이 코드에서 만약에 dessert.price 값을 읽으려고 하면 get 함수를 실행하게 되고, get 함수의 리턴 값인 cakePrice 값을 가져오게된다.

     

    만약에 dessert.price = '10$'; 라고 설정하면, 이번엔 set 함수가 실행되고 변경된 value인 '10$'가 set 함수의 인자로 들어가게 된다. 그러면 cakePrice 변수의 값이 '10$'로 바뀌게 되고 후에 다시 dessert.price에 접근하면 '10$'로 변경된 cakePrice가 출력된다.

     

     

     

    function Dessert (name, sweetness) {
      const priceLog = ['20$'];
      let cakePrice = '20$';
    
      this.dessertName = name;
      this.sweetness = sweetness;
    
      Object.defineProperty(this, 'price', {
        enumerable: true,
        get () {
          return cakePrice;
        },
        set (newPrice) {
          cakePrice = newPrice;
          priceLog.push(newPrice);
        }
      });
    
      this.getPriceLog = function () {
        return priceLog;
      }
    }
    
    const cake1 = new Dessert('lemon cake', 8);

    이제 get과 set을 사용하여 다양한 일들을 할 수 있다.

     

    Dessert라는 생성자 함수에 배열을 만들고 dessert의 price 값이 변경될 때마다

    price값을 배열에 저장하여 바뀐 값들을 기록할 수도 있다.

     

     

     

    const party = {
      host: 'Tom',
      eventName: 'birthday party',
      time: '7pm',
      location: 'ABC restaurant',
      invited: ['Sally', 'Ann', 'Paul', 'Andrew'],
      invitation: function () {
        return `You are invited to ${this.host}'s ${this.eventName}!`;
      }
    };
    
    function Dessert (occasion, dessertName, sweetness) {
      this.dessertName = dessertName;
      this.sweetness = sweetness;
    
      for (let prop in occasion) {
        if (occasion.hasOwnProperty(prop)) {
          Object.defineProperty(this, prop, {
            configurable: true,
            enumerable: true,
            get: function () {
              return occasion[prop];
            },
            set: function (changedValue) {
              occasion[prop] = changedValue;
            }
          });
        }
      }
    }
    
    const bdayParty = new Dessert(party, 'chocolate cake', 6);

     

    이렇게 생성자 함수에 다른 객체를 인자로 받아와서 속성으로 추가할 수도 있다.

     

     

    bdayParty.invited.push('Elen');
    console.log(party.invited);
    console.log(bdayParty.invited);

     

    Dessert 객체에서 생성한 인스턴스인 bdayPraty.invited에 'Elen'이란 값을 push하면

    bdayParty 인스턴스는 invited 값을 party 객체에서 읽어오고, party 객체의 invited 배열에 'Elen'을 push하게 된다.

     

     

     


    https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

     

    Object.defineProperty()

    Object.defineProperty() 정적 메서드는 객체에 직접 새로운 속성을 정의하거나 이미 존재하는 속성을 수정한 후, 그 객체를 반환합니다.

    developer.mozilla.org

     

    반응형

    COMMENT