JS 엔진의 내부 로직을 위해 ECMAScript 사양에 정의된 pseudo property와 pseudo method
JS를 통해 내부 슬롯/메소드에 직접적으로 접근할 수는 없지만, 간접적으로 일부에 접근할 수 있음
e.g. [[Prototype]]
내부 슬롯에 __proto__
를 통해 접근함
const o = {};
console.log(o.[[Prototype]]) // Uncaught SyntaxError: Unexpected token '['
console.log(o.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
JS 엔진이 property 생성 시, property attribute
를 기본값으로 자동 정의함
Property attribute: property의 상태를 나타내는 내부 슬롯
e.g. [[Value]]
, [[Writable]]
, [[Enumerable]]
, [Configurable]]
Object.getOwnPropertyDescriptor
: 한 property의 property attribute
정보를 제공하는 PropertyDescriptor
객체를 반환함const person = {
name: "J",
};
console.log(Object.getOwnPropertyDescriptor(person, "name")); // {value: 'J', writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptors
: 모든 property의 PropertyDescriptor
객체들을 반환함const person = {
firstName: "J",
};
person.lastName = "H";
console.log(Object.getOwnPropertyDescriptors(person));
// {
// "firstName": {
// "value": "J",
// "writable": true,
// "enumerable": true,
// "configurable": true
// },
// "lastName": {
// "value": "H",
// "writable": true,
// "enumerable": true,
// "configurable": true
// }
// }
키와 값으로 구성된 property
[[Value]]
: key를 통해 property에 접근 시 반환되는 값[[Writable]]
: 값 변경 가능 여부false
인 경우 읽기 전용 property가 됨[[Enumerable]]
: property의 열거 가능 여부false
인 경우 for ... in
또는 Object.keys
로 property를 열거할 수 없음[[Configurable]]
: property의 재정의 가능 여부false
인 경우 property를 삭제 or 값 변경이 불가능함[[Writable]]
이 true
라면 값 변경은 가능함값이 아닌 접근자 함수로 구성된 property로, data property의 값을 참조/저장 시 사용됨
[[Get]]
: data property 값 참조 시 호출되는 접근자 함수(getter)[[Set]]
: data property 값 저장 시 호출되는 접근자 함수(setter)[[Enumerable]]
, [[Configurable]]
const person = {
// 데이터 프로퍼티
firstName: "J",
lastName: "K",
// 접근자 프로퍼티
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
},
};
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
person.fullName = "first last";
console.log(person); // {firstName: 'first', lastName: 'last'}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.fullName); // first last
// 데이터 프로퍼티의 property attributes
console.log(Object.getOwnPropertyDescriptor(person, "firstName")); // {value: 'first', writable: true, enumerable: true, configurable: true}
// 접근자 프로퍼티의 property attributes
console.log(Object.getOwnPropertyDescriptor(person, "fullName")); // {enumerable: true, configurable: true, get: ƒ, set: ƒ}
Property attribute를 정의하는 것
Object.defineProperty
: 한 property의 property attribute
를 정의const person = {};
Object.defineProperty(person, "firstName", {
value: "J",
writable: true,
enumerable: true,
configurable: true,
});
// Descriptor 객체의 property를 일부 생략
Object.defineProperty(person, "lastName", {
value: "K",
});
console.log(Object.getOwnPropertyDescriptor(person, "firstName")); // {value: 'J', writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, "lastName")); // {value: 'K', writable: false, enumerable: false, configurable: false}
// writable === false
person.lastName = "L"; // lastName의 값이 변경되지 않고, 에러가 발생하지 않음
console.log(person.lastName); // K
// enumerable === false
// lastName을 열거할 수 없음
console.log(Object.keys(person)); // ['firstName']
// configurable === false
delete person.lastName; // lastName을 삭제할 수 없고, 에러가 발생하지 않음
console.log(person); // {firstName: 'J', lastName: 'K'}
// lastName을 재정의할 수 없음
Object.defineProperty(person, "lastName", {
writable: true,
}); // j.js:28 Uncaught TypeError: Cannot redefine property: lastName
const person = { firstName: "J", lastName: "K" };
Object.defineProperty(person, "fullName", {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(" ");
},
enumerable: true,
configurable: true,
});
person.fullName = "H L";
console.log(person); // {firstName: 'H', lastName: 'L'}
Object.defineProperties
: 여러 property의 property attribute
를 정의const person = {};
Object.defineProperties(person, {
firstName: {
value: "J",
writable: true,
enumerable: true,
configurable: true,
},
lastName: {
value: "K",
writable: true,
enumerable: true,
configurable: true,
},
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(" ");
},
enumerable: true,
configurable: true,
},
});
person.fullName = "H L";
console.log(person); // {firstName: 'H', lastName: 'L'}
Object.preventExtensions
: property 추가 금지Object.isExtensible
: 확장 가능한 객체인지 확인const person = {
firstName: "J",
};
console.log(Object.isExtensible(person)); // true
Object.preventExtensions(person);
console.log(Object.isExtensible(person)); // false
// property 추가 금지 but strict mode 아니면 에러 X
person.lastName = "K";
console.log(person); // {firstName: 'J'}
Object.seal
: property 값 읽기/쓰기만 가능Object.isSealed
: 밀봉된 객체인지 확인cf) 밀봉된 객체의 configurable
property attribute는 false
const person = {
firstName: "J",
};
console.log(Object.isSealed(person)); // false
Object.seal(person);
console.log(Object.isSealed(person)); // true
console.log(Object.getOwnPropertyDescriptors(person));
// {
// "firstName": {
// "value": "J",
// "writable": true,
// "enumerable": true,
// "configurable": false
// }
// }
// property 값 읽기/쓰기 가능
person.firstName = "H";
console.log(person.firstName); // H
// property 추가 금지 but strict mode 아니면 에러 X
person.lastName = "K";
console.log(person); // {firstName: 'H'}
// property 삭제 금지 but strict mode 아니면 에러 X
delete person.firstName;
console.log(person); // {firstName: 'H'}
// property attribute 재정의 금지
Object.defineProperty(person, "firstName", {
configurable: true,
}); // Uncaught TypeError: Cannot redefine property: firstName
Object.freeze
: property 값 읽기만 가능Object.isFrozen
: 동결된 객체인지 확인cf) 동결된 객체의 writable
/configurable
property attribute는 false
const person = {
name: "J",
};
console.log(Object.isFrozen(person)); // false
Object.freeze(person);
console.log(Object.isFrozen(person)); // true
console.log(Object.getOwnPropertyDescriptor(person, "name")); // {value: 'J', writable: false, enumerable: true, configurable: false}
// property 값 쓰기 금지 but strict mode 아니면 에러 X
person.name = "H";
console.log(person.name); // J
위의 객체 변경 방지 메소드들은 얕은 변경만 방지, 즉 직속 property만 변경이 방지된다.
const person = {
name: {
first: "J",
last: "K",
},
};
Object.freeze(person);
console.log(Object.isFrozen(person.name)); // false
person.name.first = "H";
console.log(person.name.first); // H
따라서 중첩 객체까지 동결된 읽기 전용의 불변 객체를 구현하려면, 객체를 값으로 갖는 모든 property에 대해 재귀적으로
Object.freeze
를 호출해야 한다.
function deepFreeze(target) {
if (target && typeof target === "object" && !Object.isFrozen(target)) {
Object.freeze(target);
Object.keys(target).forEach((key) => deepFreeze(target[key]));
}
return target;
}
const person = {
name: {
first: "J",
last: "K",
},
};
deepFreeze(person);
person.name.first = "H";
console.log(person.name.first); // "J"