웹공부/JS

객체 프로퍼티 설정 - 프로퍼티 플래그와 설명자

syom 2021. 10. 10. 21:55

https://ko.javascript.info/property-descriptors

객체 프로퍼티

객체에서 프로퍼티란 한 객체 내에 '키:값' 으로 구성되어 있는 것을 말한다.

let user = {
    name: 'John',
    age: 30
};

'콜론(:)' 을 기준으로 왼쪽에는 키, 오른쪽에는 값이 위치한다.
프로퍼티 키는 프로퍼티'이름' 혹은 '식별자'라고 부른다.


-> 첫번째 프로퍼티 - "name"(이름)과 "John"(값)
-> 두번째 프로퍼티 - "age"(이름)과 30(값)

프로퍼티 플래그

객체 프로퍼티는 값(value)과 함께 플래그(flag)라 불리는 특별한 속성 세 가지를 갖는다.

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

지금 까지 해왔던 '평범한 방식'으로 프로퍼티를 만들면 해당 프로퍼티의 플래그들은 모두 'true' 가 된다.
이렇게 'true'로 설정된 플래그는 언제든 수정할 수 있다.

플래그를 얻는 방법

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

- obj : 정보를 얻고자 하는 객체
- propertyName : 정보를 얻고자 하는 객체 내 프로퍼티

 

메서드를 호출하면 "프로퍼티 설명자(descriptor)"라고 불리는 객체가 반환되는데, 여기에는 프로퍼티 값과 세 플래그에 대한 정보가 모두 담겨있다.

 

예제 코드

let user = {
  name: "John"
};

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

alert( JSON.stringify(descriptor, null, 2 ) );

결과

플래그를 변경하는 방법

Object.defineProperty(obj, propertyName, descriptor)

- obj, propertyName : 설명자를 적용하고 싶은 객체와 객체 프로퍼티

- descriptor : 적용하고자 하는 프로퍼티 설명자

 

defineProperty 메서드는 객체에 해당 프로퍼티가 있으면 플래그를 원하는 대로 변경해준다.

프로퍼티가 없으면 인수로 넘겨받은 정보를 이용해 새로운 프로퍼티를 만든다.

이때 플래그 정보가 없으면 플래그 값은 자동으로 false가 된다.

 

예제

let user = {};

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

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

console.log( JSON.stringify(descriptor, null, 2 ) );

결과

 

'평범한 방식으로' 객체 프로퍼티 user.name 을 만들었을 때와

defineProperty 를 이용하여 프로퍼티를 만들었을 때 가장 큰 차이점은 플래그에 있다!

 

writable 플래그

let user = {
  name: "John"
};

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

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

* 에러는 엄격모드 에서만 발생한다

-> user.name 은 변경되지 않는다

enumerable 플래그

user 객체에 커스텀 메서드 toString 을 추가해보자.

객체 내장 메서드 toString 은 열거 불가능이라 for ... in 사용시 나타나지 않는다. 하지만 커스텀 toString 을 추가하면 for ... in 에 toString 이 나타난다.

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

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

열거 불가능한 프로퍼티는 Object.keys 에서도 배제된다.

 

configurable  플래그

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

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

내장 객체 Math 의 PI 프로퍼티가 대표적인 예이다.

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

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

이 프로퍼티는 쓰기, 열거, 구성이 불가능하고 값을 변경하거나 덮어쓰는 것도 불가능하다.

 

configurable 플래그를 false로 설정하면 돌이킬 방법이 없다. 

defineProperty를 써도 값을 true로 되돌릴 수 없다.

 

configurable:false가 만들어내는 구체적인 제약사항은 아래와 같다.

1. configurable 플래그 수정할 수 없음

2. enumerable 플래그 수정할 수 없음

3. writable: false 의 값을 true 로 바꿀 수 없음 (true -> false 는 가능)

4. 접근자 프로퍼티 get/set 을 변경할 수 없음(새로 만드는 것은 가능)

 

이런 특징을 이용하면 "영원히 변경할 수 없는" 프로퍼티(user.name)를 만들 수 있다.

let user = { };

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

// user.name 프로퍼티의 값이나 플래그를 변경할 수 없습니다.
// 아래와 같이 변경하려고 하면 에러가 발생합니다.
//   user.name = "Pete"
//   delete user.name
//   Object.defineProperty(user, "name", { value: "Pete" })
Object.defineProperty(user, "name", {writable: true}); // Error

 

Object.defineProperties(obj, descriptors)

해당 메서드를 사용하면 프로퍼티 여러 개를 한 번에 정의할 수 있다.

Object.getOwnPropertyDescriptors(obj)

해당 메서드를 사용하면 프로퍼티 설명자를 전부 한꺼번에 가져올 수 있다.

이 메서드를 Object.defineProperties와 함께 사용하면 객체 복사 시 플래그도 함께 복사할 수 있다.

let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));

지금까지는 아래와 같이 할당 연산자를 사용해 복사했었다.

for (let key in user) {
  clone[key] = user[key]
}

하지만 이렇게 복사하면 플래그는 복사되지 않는다. 플래그 까지 복사하려면 Object.defineProperties를 사용하자.