[JavaScript] 객체의 property 속성(flag)

iberis2·2023년 1월 14일
0

참고자료 : MDN 설명 defineProperty, 프로퍼티 플래그와 설명자

[함께 보면 좋은 글] 접근자 프로퍼티 getter와 setter

property의 기본 속성(flag)

객체는 key(또는 name): value 한 쌍으로 이루어진 property들로 이루어져 있다.

각 property는 flag라고 불리는 3가지 속성과 1가지 value를 가진다.
( 각 속성(flag)에 대하여 아래 자세히 설명할 예정)

  • writable : 수정 가능 여부
  • enumerable : 조회 및 열거 가능 여부
  • configurable : enumerable, configurable, writable:false → true로 수정 + 접근자 프로퍼티 수정 + delete 가능 여부

getOwnPropertyDescriptor

Object.getOwnPropertyDescriptor(객체, 'property key')
해당 property의 속성(flag) 상태(true/false)를 확인할 수 있다.

const bestFriend = { name: 'Jin Woo', age: 34,}

console.log(Object.getOwnPropertyDescriptor(bestFriend, "age"));
//{ value: 34, writable: true, enumerable: true, configurable: true }

/* 존재하지 않는 프로퍼티는 undefined 를 반환 */
console.log(Object.getOwnPropertyDescriptor(bestFriend, "birthDay")) 
// undefined

일반적인 선언 이나 할당을 통해 생성된 property의 속성(flag)의 기본값은 true 이다.

  • writable : true 다른 값으로 수정가능하고,
    enumerable: true 조회 및 열거 가능하며,
    configurable: true 속성(flag)과 접근자 프로퍼티도 수정 가능하고,
    configurable: true delete 연산자로 삭제 가능하다

한편 Object.defineProperty() 함수를 통해서도 객체의 property를 추가(또는 수정)할 수 있는데, 이 함수를 통해 만들어진 property의 속성(flag)의 기본 값은 false 이다.

  • writable : false 다른 값으로 수정 불가능하고,
    enumerable: false 조회해도 나타나지 않으며,
    configurable: false 속성(flag)와 접근자 프로퍼티도 수정 불가능하고,
    (writable: false →true로 바꿀 수 없음, 단 writable: true → false로 수정 가능)
    configurable: false delete 연산자로 삭제 불가능하다

  • (따로 지정하지 않았을 때 기본값이 false인 것일 뿐이므로) 생성할 때 각 속성(flag)별로 true 지정하여 조절할 수 있다.
/* 선언문으로 만들어진 property의 속성(flag)들의 기본값은 true 이다. */
const bestFriend = { name: "Jin Woo" };

console.log(Object.getOwnPropertyDescriptor(bestFriend, "name"));
// { value: 'Jin Woo', writable: true, enumerable: true, configurable: true }


/* defineProperty 로 만들어진 property의 속성(flag)들의 기본값은 false 이다. */
Object.defineProperty(bestFriend, "age", { value: 34 });

console.log(bestFriend.age); // 34  (값을 호출할 수는 있지만)
console.log(bestFriend); // { name: 'Jin Woo' }  (조회되지 않음)

console.log(Object.getOwnPropertyDescriptor(bestFriend, "age"));
// { value: 34, writable: false, enumerable: false, configurable: false }

defineProperty

Object.defineProperty(객체, 'property key', {데이터 또는 접근자 프로퍼티})
객체에 property와 그 property의 속성(flag)을 새로 정의(추가)하거나, 이미 존재하는 property의 속성(flag)을 수정하여 원본 객체를 반환한다.


3개의 파라미터를 가지는 데, 그 중 마지막 파라미터에는 ① property 값과 ② 속성(flag)을 어떻게 설정할지 기술한 {객체} 를 전달한다.

① property 값 : 데이터 또는 접근자로 지정

데이터 프로퍼티(또는 데이터 서술자)

을 가지는 속성(flag)을 기술할 때 사용
: value, writable 를 선택적(optional)으로 쓸 수 있으며, 지정하지 않는 경우 기본값은 value: undefined, writable: false 이다.

접근자 프로퍼티(또는 접근자 서술자)

get 과 set이 잘 이해가 안된다면 ☞ 클릭
획득자 함수 get(){}과 설정자 함수 set(){}을 한 쌍으로 가지는 속성(flag)을 기술할 때 사용

  • 데이터 프로퍼티접근자 프로퍼티를 동시에 사용할 수는 없다.
const bestFriend = {}

Object.defineProperty(bestFriend, 'name',{
  value: 'Jin Woo', 
  get(){return 'Jin woo'},  /* bestFriend.name 을 호출했을 때 리턴되는 값 */
  set(){bestFriend._name = 'Jin Woo'} /* "bestFriend.name = 어떤 값" 을 할당 했을 때 수행되는 동작 */
})
// TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>

💡 writable은 property의 속성(flag)이지만, writable과 접근자 프로퍼티get(), set() 함수 도 함께 사용할 수 없다.
- writable: true 는 property 값을 (재)할당 가능하도록 하는데, property 값을 (재)할당할 때마다 설정자 함수인 set()가 동작하므로, 결국 (재)할당 하려는 값으로 지정할 수 없다.

Object.defineProperty({}, 'name',{
  get(){return 'Jin woo'}, 
  set(){bestFriend._name = 'Jin Woo'},
  writable: true
}) // TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
    • 아무 것도 지정하지 않은 빈 객체데이터 프로퍼티로 간주한다.
      (즉, value: undefined, writable: false로 간주)
const newObj = {};
Object.defineProperty(newObj, 'empty', {});

console.log(newObj.empty); // undefined

/* writable: false 이므로 값을 수정할 수 없다.*/
newObj.empty = 0; 
// "일반 모드"에서는 아무 일이 일어나지 않고,
// "use strict" 모드에서는 TypeError: Cannot assign to read only property 'empty' of object '#<Object>' 가 발생한다.

/* 일반 모드에서 해당 프로퍼티를 다시 호출해보면 해당 값이 수정되지 않은 것을 확인할 수 있다.*/
console.log(newObj.empty); // undefined 

② property 속성(flag) 설정

writable

수정(=재할당) 가능 여부

  • writable: true일 때 해당 property 값을 변경할 수 있다.
    • =(선언 또는 할당)연산자를 사용한 property의 writable 기본값은 true이다.
      즉, 재할당 가능하다
  • writable: false일 때 해당 property 값을 변경할 수 없다.
    • Object.defineProperty()를 사용한 property의 writable 기본값은 false이다.
      즉, 재할당 불가능하다.
  • writable: false 상태에서 property 값을 수정하는 경우,
    • 일반 모드에서는 아무 일도 발생하지 않는다.
    • use strict 모드에서는 TypeError가 발생한다.
const bestFriend = {};

Object.defineProperty(bestFriend, "name", {
  value: "Jin Woo",
  writable: true, /* true로 설정하면 값을 수정할 수 있다.*/
});

console.log(bestFriend.name); // Jin Woo
bestFriend.name = "Yu JinWoo";
console.log(bestFriend.name); // Yu JinWoo

중첩된 객체 에서는 writable: false로 설정해도 중첩객체의 property 값이 변경된다.
객체는 값을 가지는 것이 아닌, 값이 담긴 주소를 참조하기 때문에, 중첩된 객체의 property 값을 변경하는 것을 막을 수 없기 때문이다.

const bestFriend = { name: { firstName: "Yu", lastName: "JinWoo" } };

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

bestFriend.name.firstName = "Kim"; 
bestFriend.name.nickName = "JJin";
delete bestFriend.name.lastName;

console.log(bestFriend); // { name: { firstName: 'Kim', nickName: 'JJin' } }

만약, 중첩 객체의 property 값 변경, 추가, 제거를 방지하고 싶은 경우 Object.freeze() 매서드를 사용하면 된다.

const bestFriend = { name: { firstName: "Yu", lastName: "JinWoo" } };

Object.freeze(bestFriend.name);

bestFriend.name.firstName = "Kim";
bestFriend.name.nickName = "JJin";
delete bestFriend.name.lastName;

console.log(bestFriend); // { name: { firstName: 'Yu', lastName: 'JinWoo' } }

enumerable

property를 조회하거나 for...in 반복문과 Object.keys()에서의 노출(열거되는 지) 여부

  • enumerable: true일 때 해당 property를 조회할 수 있다.
    • =(선언 또는 할당)연산자를 사용한 property의 enumerable 의 기본값은 true이다.
      즉, 조회 가능하다
  • enumerable: false일 때 해당 property를 조회할 수 없다.
    • Object.defineProperty()를 사용한 property의 enumerable 기본값은 false이다.
      즉, 조회 불가능하다.
const bestFriend = { name: "Jin Woo" }; /* 기본 설정은 enumerable: true*/

Object.defineProperty(bestFriend, "job", { value: "worker"}); /* 기본 설정은 enumerable: false*/
Object.defineProperty(bestFriend, "age", {
  value: "34",
  enumerable: true /* true로 설정하면 값을 조회할 수 있다.*/,
});

console.log(bestFriend); // { name: 'Jin Woo', age: '34' }
console.log(bestFriend.job); // worker (조회되진 않지만 값을 호출할 수 있다)

for (element in bestFriend) {
  console.log(element);
} // name age
console.log(Object.keys(bestFriend)); // [ 'name', 'age' ]

configurable

property의 속성(flag) enumerable, configurable, writable: false → true 변경 가능한지, delete 연산자로 property를 삭제할 수 있는지, getter와 setter를 변경할 수 있는지 여부

  • configurable: true일 때 해당 property를 삭제하거나 속성(flag)을 변경할 수 있다.
    접근자 프로퍼티get(){}``set(){}를 변경할 수 있다.
    • =(선언 또는 할당)연산자를 사용한 property의 configurable의 기본값은 true 이다.
      즉, 삭제 또는 속성(flag) 및 접근자 프로퍼티 변경이 가능하다

  • ⭐️configurable: false일 때
    ① configurable: true로 되돌릴 수 없다.
    ② enumerable 를 변경할 수 없다.
    ③ writable: false → true로 변경할 수 없다.
    ( 단, writable: true → false로 변경은 가능하다.)
    ④ 접근자 프로퍼티 get(){},set(){}를 변경할 수 없다.
    ( 단, 접근자 프로퍼티get(){}, set(){} 를 새롭게 만드는 것은 가능하다.

    • Object.defineProperty()를 사용한 property의 configurable의 기본값은 false 이다.
      즉, 삭제 또는 속성(flag) 및 접근자프로퍼티 변경 불가능하다.

❌ configurable: true → false 로 변경 가능하지만, configurable: false → true 로 되돌릴 수는 없다.

/* 기본 설정은 configurable: true*/
const bestFriend = { name: "Jin Woo", age : 34 }; 

delete bestFriend.age;
console.log(bestFriend); // { name: 'Jin Woo' }

/* configurable: false로 변경하는 경우 */
Object.defineProperty(bestFriend, 'name', {configurable: false});

/* delete 삭제 불가능하다. 
일반 모드에서는 별 다른 일이 일어나진 않고 실패(false) 학고,
'use strict' 모드에서는 TypeError: Cannot delete property 'name' of #<Object>가 발생*/
delete bestFriend.name;
/* 삭제할 수 없는 프로퍼티에 대한 delete 연산자를 콘솔에 출력해보면 false 가 나타난다. */
console.log(delete bestFriend.name) // false
console.log(bestFriend.name); // Jin woo

/* writable: true인 경우 값을 변경할 수는 있다. */
bestFriend.name = 'Yu JinWoo';
console.log(bestFriend.name); // Yu JinWoo

/* configurable: false로 변경 후에는 재변경이 불가능하다 */
Object.defineProperty(bestFriend, 'name', {
  configurable: true, 
  enumerable: false
}); 
// TypeError: Cannot redefine property: name

class에서 Object.defineProperty의 상속

class에 Object.defineProperty로 property를 추가하면서 접근자 프로퍼티get(), set() 를 사용하면,
자식 객체의 property에 접근하고 수정할 때 부모의 get과 set 메서드를 호출한다.
아래와 같이, 두 메서드가 바깥 변수에 값을 저장할 경우 모든 인스턴스 객체(자식 객체)가 property 값을 공유하게 된다.

class Bestfriends {}

let friendName;

Object.defineProperty(Bestfriends.prototype, "name", {
  get() {	 /* "인스턴스객체.name"을 호출했을 때 리턴되는 값*/
    return friendName;
  },

  set(someValue) {   /* "인스턴스객체.name = someValue"가 할당되면 동작하는 함수 */
    friendName = someValue;
  },
});

let firstFriend = new Bestfriends();
let secondFriend = new Bestfriends();

firstFriend.name = "Jin Woo";
console.log(secondFriend.name); // Jin Woo

이 문제는 this를 사용하여 각 인스턴스 객체마다 property 값을 따로 저장해서 해결할 수 있다.
함수가 호출됐을 때, this는 호출한 객체를 가리킨다.

class Bestfriends {}

let friendName;

Object.defineProperty(Bestfriends.prototype, "name", {
  get() {	  /* "인스턴스객체.name"을 호출했을 때 리턴되는 값 */
    return this.friendName;
  },

  set(someValue) {    /* "인스턴스객체.name = someValue"가 할당되면 동작하는 함수 */
    this.friendName = someValue;
  },
});

let firstFriend = new Bestfriends();
let secondFriend = new Bestfriends();

firstFriend.name = "Jin Woo";  
/* set() 매서드가 호출되며 firstFriend.friendName = "Jin Woo";가 할당된다. */

console.log(firstFriend.name); // Jin Woo
console.log(firstFriend); // Bestfriends { friendName: 'Jin Woo' }
console.log(secondFriend.name); // undefined
console.log(secondFriend); // Bestfriends {}

데이터 프로퍼티value를 사용하면 객체 자체에 설정된다.
writable: true 로 설정하지 않으면 자식 객체(인스턴스 객체)에서 해당 property 값을 수정할 수 없다.

  • property 값을 재할당할 경우 일반 모드에서는 아무 일도 일어나지 않는다
  • 'use strict' 모드에서는 TypeError가 발생한다.
class Bestfriends {}

Object.defineProperty(Bestfriends.prototype, "relationship", {
  value: "Best Friend",
});
/* writable: false 가 기본 값*/

let firstFriend = new Bestfriends();
let secondFriend = new Bestfriends();

console.log(firstFriend.relationship); // Best Friend
console.log(secondFriend.relationship); // Best Friend

firstFriend.relationship = "break off relations";
/* 'use strict' 모드에서는 TypeError: Cannot assign to read only property 'relationship' of object '#<Bestfriends>' */
console.log(firstFriend.relationship); // Best Friend

Object.defineProperties

프로퍼티 여러 개를 한 번에 정의할 수 있다.

const user = {};
Object.defineProperties(user, {
  name: { value: "John", enumerable: true },
  age: { value: 27, writable: true, enumerable: true },
  sex: { value: "male", enumerable: true, configurable: true },
});

console.log(user); // { name: 'John', age: 27, sex: 'male' };

Object.getOwnPropertyDescriptors

프로퍼티 확인하는 매서드의 복수형(s)
프로퍼티 설명자를 전부 한꺼번에 가져올 수 있다.

/* 위 user 객체 그대로 사용 */
console.log(Object.getOwnPropertyDescriptors(user));

/* { name: { value: 'John', writable: false, enumerable: true, configurable: false },
  age: { value: 27, writable: true, enumerable: true, configurable: false },
  sex: { value: 'male', writable: false, enumerable: true, configurable: true } } */

이 메서드를 Object.defineProperties와 함께 사용하면 객체 복사 시 속성(flag)도 함께 복사할 수 있다.

/* for ...in 반복문은 속성(flag)은 복사되지 않는다. */
for (let key in user) {
  clone[key] = user[key]
}

/* 속성(flag)도 함께 복사된다. */
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(user));

console.log(clone); // { name: 'John', age: 27, sex: 'male' }

/* name 프로퍼티를 수정할 수 없다. */
clone.name = "Jane";
console.log(clone.name); // John
profile
React, Next.js, TypeScript 로 개발 중인 프론트엔드 개발자

0개의 댓글