참고자료 : MDN 설명 defineProperty, 프로퍼티 플래그와 설명자
[함께 보면 좋은 글] 접근자 프로퍼티 getter와 setter
객체는 key(또는 name): value
한 쌍으로 이루어진 property
들로 이루어져 있다.
각 property는 flag
라고 불리는 3가지 속성과 1가지 value
를 가진다.
( 각 속성(flag)에 대하여 아래 자세히 설명할 예정)
writable
: 수정 가능 여부enumerable
: 조회 및 열거 가능 여부configurable
: enumerable, configurable, writable:false → true로 수정 + 접근자 프로퍼티 수정 + delete 가능 여부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)와 접근자 프로퍼티도 수정 불가능하고,configurable: false
delete 연산자로 삭제 불가능하다/* 선언문으로 만들어진 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 }
Object.defineProperty(객체, 'property key', {데이터 또는 접근자 프로퍼티})
객체에 property
와 그 property의 속성(flag)
을 새로 정의(추가)하거나, 이미 존재하는 property의 속성(flag)을 수정하여 원본 객체
를 반환한다.
3개의 파라미터를 가지는 데, 그 중 마지막 파라미터에는 ① property 값
과 ② 속성(flag)
을 어떻게 설정할지 기술한 {객체}
를 전달한다.
값을 가지는 속성(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
수정(=재할당) 가능 여부
writable: true
일 때 해당 property 값을 변경할 수 있다.=
(선언 또는 할당)연산자를 사용한 property의 writable 기본값은 true
이다.writable: false
일 때 해당 property 값을 변경할 수 없다.Object.defineProperty()
를 사용한 property의 writable 기본값은 false
이다.일반 모드
에서는 아무 일도 발생하지 않는다.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' } }
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' ]
property의 속성(flag) enumerable
, configurable
, writable: false → true
변경 가능한지, delete
연산자로 property를 삭제할 수 있는지, getter와 setter를 변경
할 수 있는지 여부
configurable: true
일 때 해당 property를 삭제하거나 속성(flag)을 변경할 수 있다.get(){}``set(){}
를 변경할 수 있다.=
(선언 또는 할당)연산자를 사용한 property의 configurable의 기본값은 true
이다.configurable: false
일 때true → false
로 변경은 가능하다.)get(){}
,set(){}
를 변경할 수 없다.get(){}
, set(){}
를 새롭게 만드는 것은 가능하다.Object.defineProperty()
를 사용한 property의 configurable의 기본값은 false
이다.❌ 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
로 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 값을 수정할 수 없다.
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
프로퍼티 여러 개를 한 번에 정의할 수 있다.
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' };
프로퍼티 확인하는 매서드의 복수형(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