JavaScript - #19. 프로퍼티 어트리뷰트
1. 내부 슬롯과 내부 메서드
프로퍼티 어트리뷰트를 이해하기 위해서는 내부 슬롯과 내부 메서드에 대한 이해가 필요하다.
내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위하여 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드이다.
내부 슬롯과 내부 메서드는 개발자가 직접 사용하기 위해 구현된 것이 아니기 때문에 원칙적으로는 외부에서 접근할 수 없다. 하지만 일부 내부 슬롯과 내부 메서드에 간접적으로 접근할 수 있는 방법이 있다.
예를들어 [[Prototype]] 이라는 내부 슬롯은 모든 객체가 갖는 내부 슬롯이다.
이 내부슬롯에 접근하기 위해 직접적으로 [[Prototype]]을 사용할 수 는 없지만.
.__proto__를 통해 간접적으로 접근할 수 있다.
const o ={};
console.log(o.[[Prototype]]); // SyntaxError
console.log(o.__proto__); // [Object: null prototype] {}
2. 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
자바스크립트 엔진에서 객체의 프로퍼티를 생성할 때 프로퍼티 어트리뷰트라는 내부슬롯을 함께 생성한다.
프로퍼티 어트리뷰트는 프로퍼티의 상태를 정의한다.
프로퍼티 상태는 다음과 같다.
- [[Value]] : 프로퍼티의 값
- [[Writable]] : 값의 갱신 가능 여부
- [[Enumerable]] : 열거 가능 여부
- [[Configurable]] : 재정의 가능 여부
위에서 알아보았듯 내부슬롯인 프로퍼티 어트리뷰트는 직접 접근할 수 없다.
하지만 Object.getOwnPropertyDescripto메서드를 사용하여 간접적으로 확인할 수 있다.
const person={
name:'Lee',
}
console.log(Object.getOwnPropertyDescriptor(person,'name')); //{ value: 'Lee', writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor 메서드르르호출할 때 첫번째 인수에는 객체의 참조를, 두번째 인수에는 확인 할 프로퍼티의 키를 문자열로 전달한다.
이때 결과값으로 나오는 객체를 프로퍼티 디스크립터 객체라고 한다. 만약 존재하지 않거나 상속받은 프로퍼티의 키에 접근을 하면 undefined를 반한다.
모든 프로퍼티에 대한 디스크립터 객체를 한번에 반환하는 방법도 있다.
const person={
name:'Lee',
age:11,
}
console.log(Object.getOwnPropertyDescriptors(person));
/*
{
name: {
value: 'Lee',
writable: true,
enumerable: true,
configurable: true
},
age: { value: 11, writable: true, enumerable: true, configurable: true }
}
*/
ES8에서 도입된 Object.getOwnPropertyDescriptors() 메서드를 사용하면 모든 프로퍼티 디스크립터 객체를 반환 할 수 있으며, 인수는 객체의 참조 하나이다.
3. 데이터 프로퍼티와 접근자 프로퍼티
프로퍼티는 값을 가지는 데이터 프로퍼티와 직접적인 값을 가지진 않지만, 다른 프로퍼티에 접근하는 접근자 프로퍼티 두가지 종류로 나뉘어 진다.
데이터 프로퍼티는 우리가 알고 있는 일반적인 프로퍼티로 앞서 알아본 내용과 동일하며, 프로퍼티가 생성되면 디스크립터 객체에서 값을 제외한 나머지 항목들은 모두 true로 초기화 된다. 동적인 추가도 마찬가지이다.
const person={
name:'Lee',
}
//{ value: 'Lee', writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(person,'name'));
// 동적 추가
person.age = 11;
//{ value: 11, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(person,'age'));
접근자 프로퍼티는 자체적인 값을 갖지않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티이다.
접근자 프로퍼티의 프로퍼티 디스크립터 객체는 데이터 프로퍼티와 차이가 있다.
프로퍼티 어트리뷰트 (접근자 프로퍼티) | 프로퍼티 디스크립터 객체 | 설명 |
[[Get]] | get | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 그결과가 프로퍼티 값으로 반환된다. |
[[Set]] | set | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다. |
[[Enumerable]] | enumerable | 데이터 프로퍼티의 [[Enumerable]]과 같다, 열거 가능 여부 |
[[Configurable]] | configurable | 데이터 프로퍼티의 [[configurable]]과 같다, 재정의 가능여부 |
코드 예시를 확인해보자.
// 접근자 프로퍼티 프로퍼티 디스크립터 확인
const user = {
name:'kim',
age:'11',
get userName(){ // 접근자프로퍼티 getter 함수
return `${this.name}의 나이는 ${this.age}입니다.`
},
set userName(userInfo){ // 접근자프로퍼티 setter 함수
[this.name, this.age] = userInfo;
}
}
// getter함수로 객체 내 데이터 프로퍼티 값 접근
console.log(user.userName); //kim의 나이는 11입니다.
// setter함수로 객체 내 데이터 프로퍼티 값 저장
user.userName=['lee',25];
// getter함수로 setter함수가 실행되었는지 확인
console.log(user.userName); //lee의 나이는 25입니다.
// 접근자 함수의 프로퍼티 디스크립터 객체
console.log(Object.getOwnPropertyDescriptor(user,'userName'))
/*
{
get: [Function: get userName],
set: [Function: set userName],
enumerable: true,
configurable: true
}
*/
getter함수와 setter함수를 사용할 때 따로 명시를하는 것이 아니라 코드에서 자동으로 문맥을 이해하고 호출하는 것을 알 수 있다.
4. 프로퍼티의 정의
프로퍼티가 정의됨과 동시에 프로퍼티 어트리뷰트는 생성된다.
위에서 알아보았듯 기본적으로 true의 값을 가지고 있지만, 정의와 동시에 사용자가 원하는 어트리뷰트로 지정해줄 수도 있다.
Object.defineProperty 메서드를 이용하여 어트리뷰트를 지정할 수 있으며, 인수에는 객체의 참조, 프로퍼티 키, 프로퍼티 디스크립터 객체 3가지가 있다.
// 프로퍼티 어트리뷰트 지정
const person2={};
// 프로퍼티 어트리뷰트를 프로퍼티 선언과 동시에 지정해준다.
Object.defineProperty(person2, 'name', {
value : 'kim',
writable : 'true',
enumerable : 'true',
configurable : 'true',
});
// 어트리뷰트를 지정하지 않으면, 기본적으로 false로 판단한다.
Object.defineProperty(person2,'age',{
value:24,
})
console.log(Object.getOwnPropertyDescriptors(person2));
/*
{
name: {
value: 'kim',
writable: true,
enumerable: true,
configurable: true
},
age: {
value: 24,
writable: false,
enumerable: false,
configurable: false
}
}
*/
각각의 어트리뷰트가 false 일때는 다음과 같이 동작한다.
// Writable : false, 쓰기
console.log(person2.age); // 24
person2.age = 30;
console.log(person2.age); // 24
// enumerable : false, 열거
console.log(Object.keys(person2)); // ['name']
//configurable: false, 재정의
delete person2.age;
console.log(person2.age); //24
이 때 오류가 발생하지 않으니 조심해야한다.
Object.defineProperties를 사용하면 여러 프로퍼티를 동시에 정의가 가능하다.
Object.defineProperties(person2,{
nation : {
value : 'Korea',
writable: true,
enumerable: true,
configurable: true
},
phoneNum : {
value : '010-0000-0000',
writable: true,
enumerable: true,
configurable: true
}
})
5. 객체 변경 방지
객체는 변경 가능한 값으로 재할당 없이 값을 직접 변경할 수 있다. 즉, 프로퍼티를 추가하거나 삭제가 가능하고 프로퍼티 값을 갱신할 수 있으며, 프로퍼티 어트리뷰트의 변경도 가능하다.
이러한 점을 방지하기 위한 다양한 메서드가 있다.
구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
객체 확장 금지 | Object.preventExtensions | x | o | o | o | o |
객체 밀봉 | Object.seal | x | x | o | o | x |
객체 동결 | Object.freeze | x | x | o | x | x |
'JavaScript Study' 카테고리의 다른 글
JavaScript - #21. 일급객체 (0) | 2023.09.18 |
---|---|
JavaScript - #20. 생성자 함수에 의한 객체생성 (0) | 2023.09.13 |
JavaScript - #18. let, const (0) | 2023.08.31 |
JavaScript - #17. 전역변수의 문제점 (0) | 2023.08.31 |
JavaScript - #16. 스코프 (0) | 2023.08.30 |