JS-객체 프로퍼티 설정

topics 200-프론트개발
types 이론 학습
tags #javascript #object
references developer.mozilla.org/ko/docs/Web/Jav... ko.javascript.info/object-properties

JS 객체 프로퍼티는 어떻게 제어하나

객체의 프로퍼티는 단순히 value만 가지는 게 아니다. value + flag로 구성되어 있다.

왜 알아야 하나

일반적으로 객체를 만들 때는 그냥 obj.name = "value" 이런 식으로 쓴다. 근데 프레임워크나 라이브러리 코드를 보면, 특정 프로퍼티를 읽기 전용으로 만들거나, 삭제 불가능하게 만들어야 할 때가 있다. 이때 프로퍼티의 flag를 제어하면 된다.

Vue나 React의 내부 구현을 보면 이런 방식으로 프로퍼티를 제어한다. (JS-proxy&defineproperty 참고)

flag

  • writabletrue이면 값을 수정할 수 있다. 그렇지 않으면 읽기만 가능
  • enumerabletrue이면 반복문을 사용해 나열할 수 있다 (for-in, Object.keys)
  • configurabletrue이면 프로퍼티 삭제나 플래그 수정이 가능. 그렇지 않으면:
    • configurable 플래그를 수정할 수 없음
    • enumerable 플래그를 수정할 수 없음
    • writable: false의 값을 true로 바꿀 수 없음 (truefalse로 변경하는 건 가능)
    • 접근자 프로퍼티 get/set을 변경할 수 없음 (새롭게 만드는 건 가능)

일반적으로 모두 true가 기본값

Object.getOwnPropertyDescriptor()

특정 객체의 프로퍼티에 대한 정보를 모두 얻을 수 있다.

let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);

obj - 객체
propertyName - key값

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()

Object.defineProperty(obj, propertyName, descriptor)

descriptor - 적용하고자하는 프로퍼티의 속성

  1. 해당 프로퍼티가 있을 때
    명시된 속성만 바꿔준다.
  2. 해당 프로퍼티가 없을 때
    명시되지 않은 속성은 다 false
let user = {};

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

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

/*
{
  "value": "John",
  "writable": false,
  "enumerable": false,
  "configurable": false
}
 */

프로퍼티 여러개 정의: Object.defineProperties(obj, descriptors)

Object.defineProperties(obj, {
  prop1: descriptor1,
  prop2: descriptor2
  // ...
});

그외의 객체 수정 방지 메서드

Object.preventExtensions(obj)
객체에 새로운 프로퍼티를 추가할 수 없게 한다.

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

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

아래 메서드는 위 세 가지 메서드를 사용해서 설정한 제약사항을 확인할 때 사용한다.

Object.isExtensible(obj)
새로운 프로퍼티를 추가하는 게 불가능한 경우 false를, 그렇지 않은 경우 true를 반환.

Object.isSealed(obj)
프로퍼티 추가, 삭제가 불가능하고 모든 프로퍼티가 configurable: false이면 true를 반환.

Object.isFrozen(obj)
프로퍼티 추가, 삭제, 변경이 불가능하고 모든 프로퍼티가 configurable: false, writable: false이면 true를 반환.

접근자 프로퍼티

객체의 프로퍼티는 두 종류가 있다:

  • 데이터 프로퍼티: 일반적인 프로퍼티
  • 접근자 프로퍼티: 데이터 프로퍼티에 접근하기 위한 프로퍼티

접근자 프로퍼티(get/set 메서드를 가짐)나 데이터 프로퍼티(value를 가짐) 중 한 종류에만 속할 수 있다.

접근자 프로퍼티의 속성

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

사용 이유

값을 원하는대로 통제할 수 있다. 예를 들어, 사용자 이름이 4글자 이상이어야 한다는 제약을 걸고 싶을 때:

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

주의: defineProperty 안에 get과 value를 동시에 설정하면 에러가 난다.

get vs defineProperty

JS-클래스에서 get/set을 사용할 때와 defineProperty를 사용할 때의 차이점:

class Foo {
  constructor () {
    Object.defineProperty(this, 'bar', {
      get () {
        return this._bar;
      },
      set (value) {
        this._bar = value;
      }
    });
  }

  get baz () {
    return this._baz;
  }

  set baz (value) {
    this._baz = value;
  }
}

const myFoo = new Foo();
console.log('bar', myFoo.hasOwnProperty('bar'));   // true
console.log('baz', myFoo.hasOwnProperty('baz'));   // false

왜 이런 차이가 생기나?

class Example1 {
  get hello() {
    return 'world';
  }
}

const obj = new Example1();
console.log(obj.hello);
// "world"

console.log(Object.getOwnPropertyDescriptor(obj, 'hello'));
// undefined

console.log(
  Object.getOwnPropertyDescriptor(
    Object.getPrototypeOf(obj), 'hello'
  )
);
// { configurable: true, enumerable: false, get: function get hello() { return 'world'; }, set: undefined }

클래스에서 get과 set을 사용하면 prototype의 프로퍼티로 설정된다. 따라서 인스턴스 자체에는 해당 프로퍼티가 없고, 프로토타입에만 존재한다.

defineProperty를 constructor 안에서 사용하면 인스턴스 자체에 프로퍼티가 추가된다.


관련 문서