나름 익숙했던 변수, 스코프, 함수 이런 내용을 다루다가 .. !
갑자기 등장해주신 프로퍼티 어트리뷰트 ,, ,, ? (너 뉘기야)
아직은 어색한 프로퍼티 어트리뷰트랑 친해지러 고고씽 ~
프로퍼티 어트리뷰트
도 어색한데 내부 슬롯
과 내부 메서드
는 또 뭘까 ? (너 뉘기야2)
프로퍼티 어트리뷰트를 이해하기 위해선, 내부 슬롯과 내부 메서드를 알아야 한다.
내부 슬롯
객체의 내부 저장 공간 | 의사(가상) 프로퍼티 (pseudo property)내부 메서드
객체가 할 수 있는 특별한 동작 | 의사(가상) 메서드 (pseudo method)자바스크립트의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용한다.
이중 대괄호로 감싼 이름 대부분이 해당한다. [[ ...]]
내부 슬롯과 내부 메서드
는 자바스크립트 엔진에서 실제로 동작하지만, 외부로 공개된 객체의 프로퍼티는 아니다.
즉 자바스크립트 엔진의 내부 로직으로, 직접 접근하거나 호출할 수 없다.
하지만 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공하기도 한다.
[[prototype]]
라는 내부 슬롯을 가짐__proto__
를 통해 간접적으로 내부 슬롯에 접근 가능아직 어트리뷰트랑 어색한데 냅다 디스크립터 객체까지 등장했다.
자 당황하지 말고..! 가보자고 !
프로퍼티 어트리뷰트
객체의 특징 (= 내부 슬롯)프로퍼티 디스크립터 객체
객체의 특징을 한 눈에 볼 수 있는 설명서프로퍼티 어트리뷰트는 자바스크립트 객체
와 관련있다.
프로퍼티 어트리뷰트
는 객체의 동작과 특성을 제어하는데, 마치 물건의 특징이나 성질로 비유할 수 있다.
프로퍼티 어트리뷰트
자바스크립트 엔진이 프로퍼티를 생성할 때, 기본값으로 자동으로 정의된 프로퍼티의 상태프로퍼티 상태
(데이터 프로퍼티)const person = {
name: 'Lee'
}
// [[Value]] = "Lee"
// [[Writable]] = true
// [[Enumerable]] = true
// [[Configurable]] = true
// (해당 상태에 접근하는 방법은 아래에 나옴)
이렇게 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트
는, 내부 슬롯이라서 직접 접근할 수 없다.
접근을 위해선, 프로퍼티 디스크립터 객체
가 필요하다.
내부슬롯인 프로퍼티 어트리뷰트에 접근하기 위해선 Object.getOwnPropertyDescriptor
메서드를 사용한다.
Object.getOwnPropertyDescriptor
메서드는 프로퍼티 디스크립터 객체
를 반환한다.
프로퍼티 디스크립터 객체
는 프로퍼티 어트리뷰트 정보를 제공한다.
프로퍼티 어트리뷰트
가 물건의 특징이라면 프로퍼티 디스크립터 객체
는 물건의 설명서로 비유할 수 있다.
만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면, undefined가 반환된다.
Object.getOwnPropertyDescriptor
첫 번째 매개변수
객체의 참조를 전달두 번째 매개변수
프로퍼티 키를 문자열로 전달Object.getOwnPropertyDescriptors
매개변수
객체의 참조를 전달두 번째 매개변수
는 없음const person = {
name: 'Lee'
}
// 1️⃣ Object.getOwnPropertyDescriptor
console.log(Object.getOwnPropertyDescriptor(person, 'name'))
// { value: "Lee", writable: true, enumerable: true, configurable: true }
person.age = 20; // 프로퍼티 동적 생성
// 2️⃣ Object.getOwnPropertyDescriptors
console.log(Object.getOwnPropertyDescriptors(person))
/* {
name: "Lee", writable: true, enumerable: true, configurable: true }
age: "20", writable: true, enumerable: true, configurable: true }
} */
프로퍼티 어트리뷰트
는 데이터 프로퍼티
와 접근자 프로퍼티
로 구분할 수 있다.
데이터 프로퍼티
키, 값으로 구성된 일반적인 프로퍼티 (지금까지 살펴본 프로퍼티가 해당됨 )접근자 프로퍼티
다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티데이터 프로퍼티
는 아래와 같이 키와 값으로 구성된 프로퍼티 어트리뷰트
를 갖는다.
프로퍼티 어트리뷰트는 자바스크립트 엔진이 프로퍼티를 생성할 때, 기본값으로 자동 정의된다.
[[Value]]
프로퍼티 어트리뷰트
[[Value]]프로퍼티 디스크립터 객체의 프로퍼티
vlaue설명
[[Value]]
값 재할당기본값
프로퍼티가 생성될 때 프로퍼티 값으로 초기화 됨[[Writable]]
프로퍼티 어트리뷰트
[[Writable]]프로퍼티 디스크립터 객체의 프로퍼티
writable설명
false
인 경우, [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티기본값
프로퍼티가 생성될 때 true로 초기화 됨[[Enumerable]]
프로퍼티 어트리뷰트
[[Enumerable]]프로퍼티 디스크립터 객체의 프로퍼티
enumerable설명
false
인 경우, for...in
문이나 Object.keys
메서드 등으로 열거 불가기본값
프로퍼티가 생성될 때 true로 초기화 됨[[Configurable]]
프로퍼티 어트리뷰트
[[Configurable]]프로퍼티 디스크립터 객체의 프로퍼티
configurable설명
false
인 경우, 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값 변경 금지true
인 경우, [[Value]] 변경과 [[Writable]]을 false로 변경 가능기본값
프로퍼티가 생성될 때 true로 초기화 됨접근자 프로퍼티
는 자체적인 값을 갖지 않는다.접근자 프로퍼티
는 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성되어 있는 프로퍼티다.[[Get]]
프로퍼티 어트리뷰트
[[Get]]프로퍼티 디스크립터 객체의 프로퍼티
get설명
[[Set]]
프로퍼티 어트리뷰트
[[Set]]프로퍼티 디스크립터 객체의 프로퍼티
set설명
[[Enumerable]]
프로퍼티 어트리뷰트
[[Enumerable]]프로퍼티 디스크립터 객체의 프로퍼티
enumerable설명
[[Configurable]]
프로퍼티 어트리뷰트
[[Configurable]]프로퍼티 디스크립터 객체의 프로퍼티
configurable설명
데이터 프로퍼티
와 접근자 프로퍼티
를 구분하기 위해 예시를 확인해보자.
const person = {
// 데이터 프로퍼티
firstName = 'sozzang';
lastName = 'Lee';
// 접근자 프로퍼티 : fullName
get fullName(){ // 읽기용 getter 함수
return `${this.firstName} ${this.lastName}`
}
set fullName(name){ // 저장용 setter 함수
[this.firstName, this.lastName] = name.split(' ')
}
}
// 1️⃣ 데이터 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.firstName + ' ' + person.lastName); // sozzang Lee
// 2️⃣ 접근자 프로퍼티를 통한 프로퍼티 값의 저장 (setter)
person.fullName = 'jjajang Lee';
console.log(person); // { firstName: 'jjajang', lastName: 'Lee' }
// 3️⃣ 접근자 프로퍼티를 통한 프로퍼티 값의 참조 (getter)
console.log(person.fullName); // 'jjajang Lee'
// 4️⃣ 데이터 프로퍼티인 firstName의 프로퍼티 어트리뷰트
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
// { value: "jjajang", writable: ture, enumerable: ture, configurable: true }
// 5️⃣ 접근자 프로퍼티인 fullName의 프로퍼티 어트리뷰트
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
// { get: f, set: f, enumerable: ture, configurable: true }
위의 예제에서 데이터 프로퍼티는 firstName
과 lastName
이다.
메서드 앞에 get과 set이 붙어 있는 메서드가 바로 getter
, setter
함수이고
getter, setter 함수의 이름인 fullName
이 접근자 프로퍼티다.
접근자 프로퍼티 fullName
으로 프로퍼티 값에 접근하면
내부적으로 [[Get]]
내부 메서드가 호출되어 다음과 같이 동작한다.
fullName
의 프로퍼티가 데이터 프로퍼티인지, 접근자 프로퍼티인지 확인fullName
의 프로퍼티 어트리뷰트 [[Get]]
의 값인 getter 함수를 호출하여 결과 반환
프로토타입
어떤 객체의 상위 객체의 역할을 하는 객체 (19장 참고)
프로퍼티 정의
란 아래의 두 가지 경우를 의미한다.
즉, 프로퍼티 자체를 정의하는 것보단, 프로퍼티 어트리뷰트
를 새로 명시하거나 재정의하는 것이다.
Object.defineProperty
인수
객체의 참조와 데이터 프로퍼티의 키 문자열, 프로퍼티 디스크립터 객체const person = {};
// 1️⃣ 데이터 프로퍼티 정의
// 인수 : 객체의 참조 (person), 데이터 프로퍼티의 키인 문자열 ('firstName'), 프로퍼티 디스크립터 객체 ( { ... } )
Object.defineProperty(person, 'firstName', {
value: 'sozzang',
writable: true,
enumerable: true,
configurable: true
})
// 2️⃣ 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
get(){
return `${this.firstName} ${this.lastName}`
},
set(name){
[this.firstName, this.lastName] = name.split(' ')
},
enumerable: true,
configurable: true
})
Object.defineProperties
Object.defineProperty
와 다르게 여러 개의 프로퍼티 어트리뷰트를 한 번에 정의할 수 있는 메서드const person = {};
Object.defineProperties(person, {
// 1️⃣ 데이터 프로퍼티 정의
firstName : {
value: 'sozzang',
// writable 정의 생략
// enumerable 정의 생략
// configurable 정의 생략
},
// 2️⃣ 접근자 프로퍼티 정의
fullName : {
get(){
return `${this.firstName} ${this.lastName}`
},
set(name){
[this.firstName, this.lastName] = name.split(' ')
},
enumerable: true,
configurable: true
},
})
다음의 값이 기본값으로 적용된다.
value
undefinedget
undefinedset
undefinedwritable
falseenumerable
falseconfiturable
false[[Writable]]의 값이 false인 경우
해당 프로퍼티의 [[Value]]의 값 변경 불가[[Enumerable]]의 값이 false인 경우
: 해당 프로퍼티는 열거되지 않음[[Confiturable]]의 값이 false인 경우
: 해당 프로퍼티 삭제/재정의 불가객체
는 변경 가능한 값이기 때문에, 재할당 없이 직접 변경이 가능하다.
즉, 프로퍼티 추가/삭제/갱신이 가능하고 프로퍼티 어트리뷰트의 재정의도 가능하다.
경우에 따라, 이러한 객체의 변경을 방지하는 메서드가 필요하다.
다음의 3가지 메서드는 객체의 변경을 금지하는 강도가 다르다.
객체 확장 금지
는, 프로퍼티 추가를 금지하는 것을 의미한다.
즉, 확장이 금지된 객체는 프로퍼티 추가가 금지된다.
프로퍼티 추가는 1️⃣ 프로퍼티 동적 추가와 2️⃣ Object.defineProperty 메서드로 가능한데, 두 가지 모두 금지된다.
Object.preventExtensions
객체 확장 금지 설정 메서드Object.isExtensible
확장 가능한 객체인지 확인하는 메서드const person = { name: 'sozzang' };
// 1️⃣ person 객체는 확장이 금지된 객체가 아님
console.log(Object.isExtensible(person)); // true
// 2️⃣ person 객체의 확장을 금지하여 프로퍼티 추가를 금지함
Object.preventExtensions(person);
// 3️⃣ person 객체는 확장이 금지됨
console.log(Object.isExtensible(person)); // false
// 4️⃣ 프로퍼티 추가 금지
person.age = 20; // 무시. strict mode에서는 에러
console.log(person); // {name: "sozzang"}
// 5️⃣ 프로퍼티 정의에 의한 추가도 금지
Object.defineProperty(person, 'age', { value: 20 });
// TypeError: Cannot define property age, object is not extensible
객체 밀봉
은, 프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트의 재정의를 금지하는 것을 의미한다.
즉, 밀봉된 객체는 프로퍼티 읽기와 쓰기만 가능하다.
Object.seal
객체 밀봉 메서드Object.isSealed
밀봉된 객체인지 확인하는 메서드const person = { name: 'sozzang' };
// 1️⃣ person 객체는 밀봉된 객체가 아님
console.log(Object.isSealed(person)); // true
// 2️⃣ person 객체를 밀봉함
Object.seal(person);
// 3️⃣ person 객체가 밀봉됨
console.log(Object.isSealed(person)); // false
// 4️⃣ 밀봉된 객체는 configurable이 false !!
console.log(Object.getOwnPropertyDescriptors(person));
/*
{ name: {value: "sozzang", writable: true, enumerable: true, configurable: false}, }
*/
// 5️⃣ 프로퍼티 추가 금지
person.age = 20; // 무시. strict mode에서는 에러
console.log(person); // {name: "sozzang"}
// 6️⃣ 프로퍼티 삭제 금지
delete person.name; // 무시. strict mode에서는 에러
console.log(person); // {name: "sozzang"}
// 7️⃣ 프로퍼티 값 갱신 가능
person.name = "jjajang";
console.log(person); // {name: "jjajang"}
// 8️⃣ 프로퍼티 어트리뷰트 재정의 금지
Object.defineProperty(person, 'name', { configurable: true });
// TypeError: Cannot redefine property: name
객체 동결
은, 프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트의 재정의를 금지, 프로퍼티 값 갱신 금지를 의미한다.
즉, 동결된 객체는 프로퍼티 읽기만 가능하다.
Object.freeze
객체 동결 메서드Object.isFrozen
동결된 객체인지 확인하는 메서드const person = { name: 'sozzang' };
// 1️⃣ person 객체는 동결된 객체가 아님
console.log(Object.isFrozen(person)); // true
// 2️⃣ person 객체를 동결함
Object.freeze(person);
// 3️⃣ person 객체가 동결됨
console.log(Object.isFrozen(person)); // false
// 4️⃣ 동결된 객체는 writable과 configurable이 false !!
console.log(Object.getOwnPropertyDescriptors(person));
/*
{ name: {value: "sozzang", writable: false, enumerable: true, configurable: false}, }
*/
// 5️⃣ 프로퍼티 추가 금지
person.age = 20; // 무시. strict mode에서는 에러
console.log(person); // {name: "sozzang"}
// 6️⃣ 프로퍼티 삭제 금지
delete person.name; // 무시. strict mode에서는 에러
console.log(person); // {name: "sozzang"}
// 7️⃣ 프로퍼티 값 갱신 금지
person.name = "jjajang"; // 무시. strict mode에서는 에러
console.log(person); // {name: "sozzang"}
// 8️⃣ 프로퍼티 어트리뷰트 재정의 금지
Object.defineProperty(person, 'name', { configurable: true });
// TypeError: Cannot redefine property: name
앞의 3가지는 객체의 얕은 변경 방지로, 직속 프로퍼티만 변경이 방지된다.
중첩 객체
는 영향을 받지 않는다.
즉, Object.freeze 메서드로 객체를 동결하여도 중첩 객체까지 동결시키는 것은 불가능하다.
const person = {
name: 'sozzang',
address: { city: 'seoul' }
};
// 얕은 객체 동결
Object.freeze(person);
// 직속 프로퍼티만 동결
console.log(Object.isFrozen(person)); // true
// 중첩 객체까지 동결하지 못함
console.log(Object.isFrozen(person.address)); // false
person.address.city = 'Suwon';
console.log(person); // {name: "sozzang", address: {city: "Suwon"}}
객체의 중첩 객체까지 동결
하여 변경이 불가능한 읽기 객체로 구현해야 한다면,
객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야 한다.
// 재귀적인 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;
}
이렇게 ... 익숙하지 않았던 프로퍼티 어트리뷰트도 완료했다 !
사실 프로퍼티 어트리뷰트는, 이름만 어색했던 것이었다. 프로퍼티의 값은 아주 익숙했는데 말이다 ..!
익숙하지 않은 용어가 많이 나왔으니까, 이해한 내용을 간략하게 정리해보자 !
프로퍼티 어트리뷰트
에 담겨있다.내부 슬롯
이기 때문에 프로퍼티 디스크립션 객체
로 접근해야 한다.데이터 프로퍼티
와 접근자 프로퍼티
로 구분된다.데이터 프로퍼티
는 보통 알고 있던 값이 포함되어 있다.접근자 프로퍼티
는 프로퍼티의 값을 읽거나 저장하기 위한 동작으로 구성된다.Object.defineProperty
메서드를 통해 프로퍼티 어트리뷰트를 새로 정의하거나 재정의할 수 있다.확장 금지
, 객체 밀봉
, 객체 동결
로 나뉜다.이해가 잘 안되는 부분은 챗지피티랑 싸워서 ... 내가 이긴 듯 하다 !
만약 .. 틀린 내용이 있다면 언제든지 댓글로 알려주세요 !