[모던 자바스크립트 Deep Dive] 16. 프로퍼티와 어트리뷰트
🎉 프로퍼티, 어트리뷰트
😉 내부 슬롯과 내부 메서드
- ECMAScript 사양에 등장하는 이중 대괄호[[…]]로 감싼 이름인 내부 슬롯과 내부 메서드는 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아니다.
-
단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공한다.
- 예를 들어, 모든 객체는
[[Prototype]]
이라는 내부 슬롯을 갖는다. [[Prototype]]
내부 슬롯의 경우,__proto__
를 통해 간접적으로 접근할 수 있다.
- 예를 들어, 모든 객체는
const o = {};
o.[[Prototype]] // Uncaught SyntaxError: Unexpected token '['
o.**proto** // Object.prototype
😉 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
- 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
- 프로퍼티 어트리뷰트 : 자바스크립트 엔진이 관리하는 내부 상태 값 인 내부 슬롯
- 프로퍼티의 값
[[Value]]
- 값을 갱신 가능 여부
[[Writable]]
- 열거 가능 여부
[[Enumerable]]
- 재정의 가능 여부
[[Configurable]]
- 프로퍼티의 값
- Object.getOwnPropertyDescriptor 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다. 만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요규하면 undefined가 반환된다.
const person = {
name: 'Lee'
};
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다.
console.log(Object.getOwnPropertyDescriptor(person, 'name');
// {value: 'Lee', writable: true, enumerable: true, configurable: true}
😉 데이터 프로퍼티와 접근자 프로퍼티
🐱🐉 데이터 프로퍼티
키와 값으로 구성된 일반적인 프로퍼티
데이터 프로퍼티의 프로퍼티 어트리뷰트
1] [[Value]]
- 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값
- 프로퍼티 키를 통해 프로퍼티 값을 변경하면
[[Value]]
에 값을 재할당한다. 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값을 지정한다.
2] [[Writable]]
- 프로퍼티 값의 변경 기능 여부를 나타내며 불리언 값을 갖는다.
[[Writable]]
값이 false인 경우, 해당 프로퍼티[[Value]]
의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다.
3] [[Enumerable]]
- 프로퍼티 열거 기능 여부를 나타내며 불리언 값을 갖는다.
[[Enumerable]]
의 값이 false인 경우 해당 프로퍼티는 for..in문이나 Object.keys메서드 등으로 열거 할 수 없다.
4] [[Configurable]]
- 프로퍼티의 재정의(다시정의) 가능 여부를 나타내며 불리언 값을 갖는다.
[[Configurable]]
의 값이 false인 경우 해당 프로퍼티의 삭제,어트리뷰트 값의 변경이 금지 된다. 단,[[Writable]]
이 true인 경우[[Value]]
의 변경과[[Writable]]
은 false로 변경하는 것은 허용된다.
프로퍼티가 생성될 때 [[Value]]
의 값은 프로퍼티 값으로 초기화되며 [[Writable]]
, [[Enumerable]]
, [[Configurable]]
의 값이 true로 초기화된다.
🐱🐉 접근자 프로퍼티
자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 대 호출되는 접근자 함수로 구성된 프로퍼티다.
접근자 프로퍼티의 프로퍼티 어트리뷰트
1] [[Get]]
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수
- 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉
getter 함수
가 호출되고 그 결과가 프로퍼티 값을 반환한다. return
이 꼭 있어야 한다.
2] [[Set]]
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 지정할 때 호출되는 접근자 함수
- 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉
setter 함수
가 호출되고 그 결과 프로퍼티 값으로 저장된다.
3] [Enumerable]] 데이터 프로퍼티와 동일하다.
4] [[Configurable]] 데이터 프로퍼티와 동일하다.
const person = {
//데이터 프로퍼티
firstName = 'Ungmo',
lastName = 'Lee',
//접근자 프로퍼티
//getter함수
get fullName(){
return `${this.firstName} ${this.lastName}`;
},
//setter함수
set fullName(){
[this.firstName, this.lastName] = name.split(' ');
}
};
1] 데이터 프로퍼티를 통한 프로퍼티 값 참조
consoel.log(person.firstName + " " + person.lastName);
//Ungmo Lee
2] 접근자 프로퍼티를 통한 프로퍼티 값의 저장
- 접근자 프로퍼티 fullName에 값을 저장하면 setter함수가 호출된다.
person.fullName = "Heegun Lee";
console.log(person);
//{firstName : 'Heegun', lastName : 'Lee'}
3] 접근자 프로퍼티를 통한 프로퍼티 값의 참조
- 접근자 프로퍼티 fullName에 접근하면, getter 함수가 호출된다.
console.log(person.fullName);
//Heegun Lee
4] 접근자 프로퍼티의 어트리뷰트 출력
- [[Get]] [[Set]] [[Enumerable]] [[Configurable]]을 가지고 있다.
let descriptor = Object.getOwnPropertyDescriptor(person, "fullName");
console.log(descriptor);
//{get : f, set : f, enumerable : true, configurable : true}
🐱🐉 프로토타입(Prototype)
어떤 객체의 상위(부모) 객체의 역활을 하는 객체
- 하위(자식)객체에게 자신의 프로퍼티와 메서드를 상속한다.
- 상속 받은 하위 객체는 자신의 프로퍼티 또는 메서드의 것처럼 자유롭게 사용할 수 있다.
- 프로토타입 체인은 프로토타입이 단반향 링크드 리스트 형태로 연결되어 있는 상속 구조이다.
- 객체의 프로퍼티나 메서드에 접근하려고 할때 해당 객체에 접근하려는 프로퍼티 또는 메서드가 없다면 프로토타입 체인을 따라 프로토타입의 프로토타입의 프로퍼티나 메서드를 차례로 검색한다.
😉 프로퍼티 정의
- 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의
- 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것 → Object.defineProperty와 Object.definePropertes 메서드 사용
- Object.defineProperty : 한번에 하나의 프로퍼티만 정의
- Object.definePropertes : 여러개의 프로퍼티를 한 번에 정의
const person ={};
//데이터 프로퍼티 정의
Object.defineProperty(person.'firstName',{
value : 'Ungmo',
writable : true,
enumerable : true,
configurable : true
});
Object.defineProperty(person. 'lastName', {
vaule: 'Lee'
});
//접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName',{
get(){
return `${this.firstName}, ${this.lastName}`;
},
set(name){
[this.firstName, this.lastName] = name.split(' ');
},
enumerable : true,
configurable : true
});
- [[writable]] [[enumerable]] [[configurable]] 생략 가능
- 디스크립터 객체의 프로퍼티를 누락 시 기본값
- value, get, set : undefined
- wirtable, enumerable, configurable : false
😉 객체 변경 방지
🐱🐉 객체 확장 금지
확장이 금지된 객체는 프로퍼티 추가가 금지된다. (동적 추가, Object.defineProperty메서드)
- 메서드 : Object.preventExtensions
- 확장 확인 메서드 : Object.isExtensible
const person = { name: "Lee" };
//person 객체는 확장이 금지된 객체가 아니다.
console.log(Object.isExtension(person)); //true
//person 객체의 확장을 금지하여 프로퍼티 추가를 금지한다.
Object.preventExtension(person);
//person 객체는 확장이 금지된 객체이다.
console.log(Object.isExtension(person)); //false
- 프로퍼티 추가 금지된다.
person.age = 20;
//무시. strict mode에서는 에러
console.log(person);
//{name : 'Lee'}
- 프로퍼티 추가는 금지되지만 삭제는 가능하다.
delete.person.name;
console.log(person); //{}
- 프로퍼티 정의에 의한 프로퍼티 추가도 금지된다.
Object.defineProperty(person, "age", { value: 20 });
//TypeError : Cannnot define property age, object is not extensible
🐱🐉 객체 밀봉
Object.seal 메서드
밀봉된 객체는 읽기와 쓰기만 가능하다.
- 추가, 삭제, 재정의를 금지
Object.isSealed
메서드로 밀봉 객체 여부 확인 가능
const person = { name: "Lee" };
//person 객체는 밀봉된 객체가 아니다.
console.log(Object.isSealed(person)); //false
//person 객체의 밀봉하여 프로퍼티 추가, 삭제, 재정의를 금지한다.
Object.Seal(person);
//person 객체는 밀봉된 객체이다.
console.log(Object.isSealed(person)); //true
//밀봉된 객체는 configurable이 false이다.
console.log(Object.getOwnPropertyDescriptor(person));
/*{name : {value : 'Lee', wirtable : true, enumerable : true, configurable : false}*/
- 프로퍼티 추가가 금지된다.
- 프로퍼티 삭제가 금지된다.
delete.person.name;
//무시. strict mode에서는 에러
console.log(person); //{name : 'Lee'}
- 프로퍼티 값 갱신은 가능하다.
person.name = "Kiem";
console.log(person); //{name : 'Kim'}
- 프로퍼티 어트리뷰트 재정의가 금지된다.
Object.defineProperty(person, "name", { configurable: true });
//TypeError : Cannnot redefine propery : name
🐱🐉 객체 동결
Object.freeze 메서드
동결된 객체는 읽기만 가능하다.
- 프로퍼티 추가, 삭제, 재정의, 쓰기 금지
- Objct.isFrozen 메서드 동결 객체인지 여부 확인 가능
const person = { name: "Lee" };
//person 객체는 동결된 객체가 아니다.
console.log(Object.isFrozne(person)); //false
//person 객체의 동결하여 프로퍼티 추가, 삭제, 재정의, 쓰기가 금지한다.
Object.Freeze(person);
//person 객체는 동결된 객체이다.
console.log(Object.isFrozne(person)); //true
//밀봉된 객체는 Writable이 false이다.
console.log(Object.getOwnPropertyDescriptor(person));
/*{name : {value : 'Lee', wirtable : false, enumerable : true, configurable : false}*/
-
프로퍼티 추가가 금지된다.
-
프로퍼티 삭제가 금지된다.
-
프로퍼티 값 갱신이 금지된다.
person.name = "Kim";
//무시, strict mode에서 에러
console.log(person);
//{name : 'Lee'}
- 프로퍼티 어트리뷰트 재정의가 금지된다.
🐱🐉 불변 객체
객체 확장 금지, 객체 밀봉, 객체 동결은 얕은 변경 방식으로 중첩 객체까지 동결 할 수 없다. 따라서, 재귀적으로 Object.freeze 메서드를 호출해야 한다.
fucntion 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 : 'Lee',
address : {city : 'Seoul'}
};
//깊은 객체 동결
deepFreeze(person);
console.log(Object.isFrozen(person)); //true
console.log(Objdec.isFrozen(person.address)); //true
person.address.city = 'Busan';
console.log(person.address);
//city : 'Seoul'
Leave a comment