JavaScript
[모던 자바스크립트]프로퍼티 어트리뷰트
jjin502
2022. 8. 17. 20:02
내부 슬롯과 내부 메서드
- 내부 슬롯과 내부 메서드 : 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript사양에서 사용하는 의사 프로퍼티와 의사 메서드.
- 형태 : [[...]] (이중 대괄호로 감싸져있음)
- 개발자가 직접 접근할 수 없음.
- 간접적으로 접근이 가능함
// 16-1
const o = {};
// 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 직접 접근 불가능
o.[[Prototype]]
// SyntaxError: Unexpected token '['
// 단, 일부 내부 슬롯과 내부 메서드에 한해 간접적으로 접근할 수 있는 수단을 제공하긴 함.
o.__proto__; // Object.prototype
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의함.
- 프로퍼티의 상태 : 프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부를 말함.
- 프로퍼티 어트리뷰트 : JS엔진이 관리하는 내부 상태 값인 내부 슬롯
- [[Writeable]], [[Value]], [[Enumerable]], [[Configurable]],..
- 직접 접근 불가능, but Object.getOwnPropertyDescriptor 메서드로 간접적으로 확인 가능
// 16-2
const person = {
name: "Sim"
};
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환함.
console.log(Object.getOwnPropertyDescriptor(person, "name"));
// 첫 번째 매개변수 : 객체의 참조를 전달. 두 번째 매개변수 : 프로퍼티 키를 문자열로 전달.
// {value: "Sim", writable: true, enumerable: true, configurable: true}
// 프로퍼티 디스크립터 객체를 반환
// 16-3
const person = {
name: "Sim"
};
// 프로퍼티 동적 생성
person.age = 18;
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환함.
console.log(Object.getOwnPropertyDescriptor(person));
// {value: "Sim", writable: true, enumerable: true, configurable: true}
데이터 프로퍼티와 접근자 프로퍼티
어떻게 구분하는 가 ?
- 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티
- 접근자 프로퍼티 : 지체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
데이터 프로퍼티
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
[[Writeable]] | writeable | - 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 가짐 - [[Writeable]]의 값이 false인 경우 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다. |
[[Value]] | value | - 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값. - 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재 할당함. 이때 프로퍼티가 없으면, 동적으로 생성하고, [[Value]]에 값을 저장 |
[[Enumerable]] | enumerable | - 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 가짐 - [[Enumerable]]의 값이 false인 경우 해당 프로퍼티는 for ... in문이나 Object.keys 메서드 등으로 열거할 수 없음. |
[[Configurable]] | configurable | - 프로퍼티 재정의 가능 여부를 나타내며 불리언 값을 가짐. - [[Configurable]]의 값이 false인 경우 해당 프로퍼티의 삭제 프로퍼티 어트리뷰트 값의 변경이 금지됨. 단, [[Writeable]]의 값이 true인 경우 [[Value]]의 변경과 [[Writeable]]을 false로 변경하는 것은 허용. |
// 16-4
const person = {
name: "Sim"
};
// 프로퍼티 동적 생성
person.age = 18;
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 취득함.
console.log(Object.getOwnPropertyDescriptor(person, "name"));
// {value: "Sim", writable: true, enumerable: true, configurable: true}
// 16-5
const person = {
name: "Sim"
};
// 프로퍼티 동적 생성
person.age = 18;
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 취득함.
console.log(Object.getOwnPropertyDescriptor(person));
// {value: "Sim", writable: true, enumerable: true, configurable: true}
- 프로퍼티가 생성될 때 [[Value]]의 값은 프로퍼티 값으로 초기화,[[Writeable]], [[Enumerable]], [[Configurable]] 의 값은 true로 초기화
- 프로퍼티를 동적으로 추가해도 마찬가지
접근자 프로퍼티
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
[[Get]] | get | - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면, 프로퍼티 어트리 뷰트 [[Get]]의 값 즉, getter함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다. |
[[Set]] | set | - 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 저장할 때 호출되는 접근자 함수다. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면, 프로퍼티 어트리뷰트 [[Set]] 의 값, 즉 setter함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다. |
[[Enumerable]] | enumerable | - 데이터 프로퍼티와 같음. |
[[Configurable]] | configurable | - 데이터 프로퍼티와 같음. |
- 접근자 함수는 getter/setter 함수라고도 함
- 접근자 프로퍼티는 둘 다 정의 할 수도 있고, 하나만 정의할 수도 있음.
// 16-6
const person = {
// 데이터 프로퍼티
firstName: "Mijin",
lastName: "Sim",
// fullName 은 접근자 함수로 구성한 접근자 프로퍼티
// getter함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set fullName(name) {
// 배열 디스트럭처럼 할당: "31.1배열 디스크럭처럼 할당 참고"
[this.firstName, this.lastName] = name.split(" ");
}
};
// 데이터 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.firstName + " " + person.lastName); // Mijin Sim
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter함수가 호출됨.
person.fullName = "Hyunjin Sim";
console.log(person); // {firstName: "Hyunjin", lastName: "Sim", fullName: "Hyunjin Sim"}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출됨.
console.log(person.fullName);
// Hyunjin Sim
// firtName은 데이터 프로퍼티
// 데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]]
// 프로퍼티 어트리뷰트를 가짐.
let descripter = Object.getOwnPropertyDescriptor(person, "firstName");
console.log(descripter);
// {value: "Hyunjin", writable: true, enumerable: true, configurable: true}
// fullName은 접근자 프로퍼티
// 접근자 프로퍼티는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]]
// 프로퍼티 어트리뷰트를 가짐.
descripter = Object.getOwnPropertyDescriptor(person, "fullName");
console.log(descripter);
// {get: ƒ get fullName(), set: ƒ set fullName(), enumerable: true, configurable: true}
내부 슬롯/메서드 관점에서 보자면 ....
프로퍼티 키가 유효한지 확인.
- 프로퍼티 키는 문자열 또는 심벌이어야 함.
- 프로퍼티 키 "fullName" 은 문자열 이므로 유효한 프로퍼티 키다.
프로토타입 체인에서 프로퍼티를 검색.
- person객체에서 fullName 프로퍼티가 존재함.
검색된 fullName 프로퍼티가 데이터 프로퍼티인지 전근자 프로퍼티인지 확인
- fullName 프로퍼티는 접근자 프로퍼티이다.
접근자 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]] 의 값, 즉 getter함수를 호출해 그 결과를 반환.
- 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]] 의 값은 Object.getOwnPropertyDescriptor 메서드가 반환하는 프로퍼티 디스크립터 객체의 get프로퍼티 값과 같다.
흠 .. 프로토타입이 뭐죠 .. ?
- 어떤 상위(부모) 객체의 역할을 하는 객체
- 하위(자식)객체에 프로퍼티와 메서드를 상속함.
- 상속받은 하위객체는 자신의 프로퍼티 또는 메서드 처럼 사용할 수 있음.
그런데 접근자 프로퍼티와 데이터 프로퍼티를 어떻게 구분해야하나요 .. ?
- 이렇게 !!
// 16-7
// 일반 객체의 __proto__는 접근자 프로퍼티이다.
Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
// 함수 객제의 prototype은 데이터 프로퍼티다.
Object.getOwnPropertyDescriptor(function () {}, "prototype");
프로퍼티 정의
- 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것.
- Object.defineProperty 메서드 사용하면 가능함
// 16-8
const person = {};
// 데이터 프로퍼티 정의
Object.defineProperty(person, "firstName", {
value: "Mijin",
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(person, "lastName", {
value: "Sim"
});
let descripter = Object.getOwnPropertyDescriptor(person, "firstName");
console.log("firstName", descripter);
// fisrtName {value: "Mijin", writable: true, enumerable: true, configurable: true}
// 디스크립터 객체의 프로퍼티를 누락시키면 undefined,false가 기본 값.
descripter = Object.getOwnPropertyDescriptor(person, "lastName");
console.log("lastName", descripter);
// lastName {value: "Sim", writable: false, enumerable: false, configurable: false}
// [[Enumerable]]의 값이 false인 경우
// 해당 프로퍼티는 for ... in 문이나 Object.keys 등으로 열거할 수 없음.
// lastName프로퍼티는 [[Enumerable]]의 값이 false이므로 열겨되지 않음
console.log(Object.keys(person)); // ["firstName"]
// [[Writeable]]의 값이 false인 경우 해당 프로퍼티의 [[Value]]의 값을 변경 할 수 없음.
// lastName프로퍼티는 [[Writeable]]의 값이 false이므로 변경할 수 없다.
// 이때 프로퍼티를 변경하면 에러 발생 하지 않고 무시됨
person.lastName = "Sim";
// [[Configurable]]의 값이 false인 경우 해당 프로퍼티를 삭제 할 수 없음.
// lastName프로퍼티는 [[Configurable]]의 값이 false이므로 변경할 수 없다.
// 이때 프로퍼티를 삭제하면 에러 발생 하지 않고 무시됨
delete person.lastName;
// [[Configurable]]의 값이 false인 경우 해당 프로퍼티를 재정의 할 수 없음.
// Object.defineProperty(person,'lastName',{enumerable:true});
// Uncaught TypeError: Cannot redefine property: lastName
descripter = Object.getOwnPropertyDescriptor(person, "lastName");
console.log("lastName", descripter);
// lastName {value: "Sim", 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
});
descripter = Object.getOwnPropertyDescriptor(person, "fullName");
console.log("fullName", descripter);
// fullName {get: ƒ get(), set: ƒ set(), enumerable: true, configurable: true}
person.fullName = "Hyunjin Sim";
console.log(person);
// {firstName: "Hyunjin", fullName: "Hyunjin Sim"}
- Object.defineProperty 메서드는 한 번에 한 번만 정의가 가능함.
- Object.defineProperties를 쓰면 한 번에 사용 가능
// 16-9
const person = {};
Object.defineProperty(person, {
// 데이터 프로퍼티 정의
firstName: {
value: "Mijin",
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: "Sim",
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 = "Hyunjin Sim";
console.log(person);
// {firstName: "Hyunjin", fullName: "Hyunjin Sim"}
객체 변경 방지
구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
객체 밀봉 | Object.seal | X | X | O | O | X |
객체 동결 | Object.freeze | X | X | X | X |
객체 확장 금지
// 16-10
const person = { name: "Sim" };
// person객체는 확장이 금지된 객체가 아니다.
console.log(Object.isExtensible(person)); // true
// person 객체의 확장을 금지하여 프로퍼티 추가를 금지함.
Object.preventExtensions(person);
// person 객체는 확장이 금지된 객체다.
console.log(Object.isExtensible(person));
// 프로퍼티 추가가 금지됨
person.age = 18;
console.log(person);
// 삭제는 가능
delete person.name;
console.log(person);
// 프로퍼티 정의에 의한 프로퍼티 추가도 금지됨
Object.defineProperty(person, "age", { value: 18 });
// TypeError: Cannot define property afe, object is not extensiable
객체 밀봉
- 읽기, 쓰기만 가능
// 16-11
const person = { name: "Sim" };
// person객체는 밀봉된 객체가 아니다.
console.log(Object.isSealed(person)); // false
// person 객체의 밀봉하여 프로퍼티 추가,삭제,재정의를 금지함.
Object.seal(person);
// person 객체는 밀봉된 객체다.
console.log(Object.isSealed(person));
// 밀봉된 객체는 configurable이 false다.
console.log(Object.getOwnPropertyDescriptors(person));
// 프로퍼티 추가 금지됨
person.age = 20;
console.log(person);
// 프로퍼티 삭제 금지됨
delete person.name;
console.log(person);
// 갱신 가능
person.name = "Lee";
console.log(person);
// 프로퍼티 어트리뷰트 재정의가 금지된다.
Object.defineProperty(person, "name", { configurable: true });
// TypeError: Cannot redfine property: name
객체 동결
- 읽기만 가능
// 16-12
const person = { name: "Sim" };
// person객체는 동결된 객체가 아니다.
console.log(Object.isFrozen(person)); // false
// person 객체의 동결하여 프로퍼티 추가,삭제,재정의,쓰기를 금지함.
Object.freeze(person);
// person 객체는 동결된 객체다.
console.log(Object.isFrozen(person));
// 밀봉된 객체는 configurable이 false다.
console.log(Object.getOwnPropertyDescriptors(person));
// 동결된 객체는 writable과 configurable이 false다.
console.log(Object.getOwnPropertyDescriptors(person));
// 프로퍼티 추가 금지됨
person.age = 20;
console.log(person);
// 프로퍼티 삭제 금지됨
delete person.name;
console.log(person);
// 갱신 금지
person.name = "Lee";
console.log(person);
// 프로퍼티 어트리뷰트 재정의가 금지된다.
Object.defineProperty(person, "name", { configurable: true });
// TypeError: Cannot redfine property: name
불변 객체
- 위 메서드들은 얕은 변경 방지로 직속 프로퍼티만 변경이 방지되고, 중첩에는 영향을 미치지 않음.
// 16-14
function deepFreeze(target) {
// 객체가 아니거나 동결된 객체는 무시, 동결되지 않은 객체만 동결
if (target && typeof target === "object" && !Object.isFrozen(target)) {
Object.freeze(target);
/*
모든 프로퍼티를 순회하며 재귀적으로 동결함
Object.keys매서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환.
forEach매서드는 배열을 순회하며 배열의 각 요소에 대해 콜백 함수를 실행
*/
Object.keys(target).forEach((key) => deepFreeze(target[key]));
}
return target;
}
const person = {
name: "Sim",
address: { city: "Ulsan" }
};
// 얕은 객체 동결
deepFreeze(person);
// 직속 프로퍼티만 동결
console.log(Object.isFrozen(person)); // false
// 중첩 객체까지 동결.
console.log(Object.isFrozen(person.address));
person.address.city = "Busan";
console.log(person);