모던 자바스크립트 Deep Dive 정리
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의
데이터 프로퍼티
▶ 프로퍼티를 생성할 때 자동 정의되는 요소들
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 | |
데이터 프로퍼티 | [[Value]] | value | - 프로퍼티 밸류값 |
[[Writable]] | writable | - 프로퍼티 밸류값 변경 가능여부 - false 이면 읽기 전용 |
|
[[Enumerable]] | enumerable | - 열거 가능 여부 - false 이면 for .. in 또는 Object.keys() 메서드로 열거 불가 |
|
[[Configurable]] | configurable | - 재정의 가능 여부 - false 이면 프로퍼티 삭제, 프로퍼티 어트리뷰트 변경 금지 - Writable이 true일 때는 프로퍼티 밸류값과 Writable 어트리뷰트 변경 가능 |
▷ 예시
const person = {
name: 'Lee'
};
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다.
console.log(Object.getOwnPropertyDescriptor(person, 'name')); // name 프로퍼티 대상
// {value: "Lee", writable: true, enumerable: true, configurable: true}
// 프로퍼티 동적 생성
person.age = 20;
// 모든 프로퍼티의 프로퍼티 어트리뷰트 정보도 확인 가능
console.log(Object.getOwnPropertyDescriptors(person));
/*
{
name: {value: "Lee", writable: true, enumerable: true, configurable: true},
age: {value: 20, writable: true, enumerable: true, configurable: true}
}
*/
접근자 프로퍼티
▶ 데이터 프로퍼티 값을 읽거나 저장할 때 호출하는 접근자 함수로 구성됨
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 | |
접근자 프로퍼티 | [[Get]] | get | - 데이터 프로퍼티의 값을 읽는 getter 함수 |
[[Set]] | set | - 데이터 프로퍼티의 값을 저장하는 setter 함수 | |
[[Enumerable]] | enumerable | 데이터 프로퍼티와 동일 | |
[[Configurable]] | configurable | 데이터 프로퍼티와 동일 |
▷ 예시
const person = {
firstName: 'Harry',
lastName: 'Kane',
// fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
};
console.log(person.firstName + ' ' + person.lastName); // Harry Kane
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다.
person.fullName = 'Heung-Min Son'; // 동적으로 프로퍼티 생성
console.log(person); // {firstName: "Heung-Min", lastName: "Son"}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(person.fullName); // Heung-Min Son
// firstName은 데이터 프로퍼티다.
// 데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
// {value: "Heung-Min", writable: true, enumerable: true, configurable: true}
// fullName은 접근자 프로퍼티다.
// 접근자 프로퍼티는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}
프로퍼티 정의
▶ 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나
▶ 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의할 수 있다
▶ Object.defineProperty() 메서드를 이용
const person = {};
// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
value: 'Heung-Min',
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(person, 'lastName', {
value: 'Son'
});
// 디스크립터 객체의 프로퍼티를 누락시키면 undefined, false가 기본값이다.
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
// lastName {value: "Son", writable: false, enumerable: false, configurable: false}
// [[Enumerable]]의 값이 false인 경우
// 해당 프로퍼티는 for...in 문이나 Object.keys 등으로 열거할 수 없다.
// lastName 프로퍼티는 [[Enumerable]]의 값이 false이므로 열거되지 않는다.
console.log(Object.keys(person)); // ["firstName"] -> 원래라면 ['firstName, 'lastName']
// [[Writable]]의 값이 false인 경우 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없다.
// lastName 프로퍼티는 [[Writable]]의 값이 false이므로 값을 변경할 수 없다.
// 이때 값을 변경하면 에러는 발생하지 않고 무시된다.
person.lastName = 'Kim';
// [[Configurable]]의 값이 false인 경우 해당 프로퍼티를 삭제할 수 없다.
// lastName 프로퍼티는 [[Configurable]]의 값이 false이므로 삭제할 수 없다.
// 이때 프로퍼티를 삭제하면 에러는 발생하지 않고 무시된다.
delete person.lastName;
// [[Configurable]]의 값이 false인 경우 해당 프로퍼티를 재정의할 수 없다.
// Object.defineProperty(person, 'lastName', { enumerable: true });
// Uncaught TypeError: Cannot redefine property: lastName
// 아무것도 변경 안됨
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
// lastName {value: "Son", writable: false, enumerable: false, configurable: false}
// 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
// getter 함수
get() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
});
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log('fullName', descriptor);
// fullName {get: ƒ, set: ƒ, enumerable: true, configurable: true}
person.fullName = 'Harry Kane';
console.log(person); // {firstName: "Harry", lastName: "Kane"}
프로퍼티 정의 시 인수를 전달하지 않으면?
// defineProperty는 1개의 프로퍼티 어트리뷰트만 정의
Object.defineProperty(person, 'lastName', {
value: 'Lee' // value만 전달
});
// defineProperties는 여러개의 프로퍼티 어트리뷰트 한 번에 정의
const person = {};
Object.defineProperties(person, {
// 데이터 프로퍼티 정의
firstName: {
value: 'Heung-Min',
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: 'Son',
writable: true,
enumerable: true,
configurable: true
},
// 접근자 프로퍼티 정의
fullName: {
// getter 함수
get() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
}
});
person.fullName = 'Harry Kane';
console.log(person); // {firstName: "Harry", lastName: "Kane"}
undefined, false가 기본값으로 정의된다
프로퍼티 디스크립터 객체의 프로퍼티 | 대응하는 프로퍼티 어트리뷰트 | 생략했을 때의 기본값 |
value | [[Value]] | undefined |
get | [[Get]] | undefined |
set | [[Set]] | undefined |
writable | [[Writable]] | false |
enumerable | [[Enumerable]] | false |
configurable | [[Configurable]] | false |
객체 변경 방지
▶ 객체의 변경을 방지하는 다양한 메서드가 있고, 그마다 금지 강도가 다르다
▷ 아래로 갈수록 강하게 금지
구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
객체 확장 금지 | - 설정: Object. preventExtensions(객체명) - 확인: Object.isExtensible(객체명) |
X | O | O | O | O |
객체 밀봉 | - 설정: Object.seal(객체명) - 확인: Object.isSealed(객체명) |
X | X | O | O | X |
객체 동결 | - 설정: Object.freeze(객체명) - 확인: Object.isFrozen(객체명) |
X | X | O | X | X |
▶ 프로퍼티 추가 2가지 방법(동적 추가, defineProperty 계열 메서드)이 있는데 모두 금지
▶ 중첩 객체에는 영향 X (중첩 객체까지 변경 방지하려면 재귀 함수 사용)