diff --git "a/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/ch16.md" "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/ch16.md" new file mode 100644 index 0000000..0ed3acc --- /dev/null +++ "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/ch16.md" @@ -0,0 +1,317 @@ +# 16. 프로퍼티 어트리뷰트 + +Created: 2024년 8월 11일 오후 10:19 + +# Chapter 16 프로퍼티 어트리뷰트 + +## 주요 내용 정리 + +### 내부 슬롯과 내부 메서드 + +- 내부 슬롯 : 의사 프로퍼티 +- 내부 메서드 : 의사 메서드 + + + +```jsx +const o = {}; + +// 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 직접 접근할 수 없다. +o.[[Prototype]] // -> Uncaught SyntaxError: Unexpected token '[' +// 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공하기는 한다. +o.__proto__ // -> Object.prototype +``` + +- 모든 객체는 [[Prototype]] 이라는 내부 슬롯을 가진다. +- 자바스크립트 엔진의 내부 슬롯은 원칙적으로 직접 접근할 수 없다. + - 하지만 [[Prototype]] 내부 슬롯은, `__proto__` 를 통해 간접적으로 접근할 수 있다. + +### 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체 + + + +- 프로퍼티 상태란? + - 프로퍼티의 값 (value) + - 값의 갱신 가능 여부 (writable) + - 열거 가능 여부 (enumerable) + - 재정의 가능 여부 (configurable) +- 프로퍼티 어트리뷰트 + - `[[Value]]` + - `[[Writable]]` + - `[[Enumerable]]` + - `[[Configurable]]` + + ⇒ 어트리뷰트에 직접 접근할 수 없다. + + - 하지만, **`Object.getOwnPropertyDescriptor`** 메서드로 간접적 확인은 가능하다. + + ```jsx + const person = { + name: 'Lee' + }; + + // 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다. + console.log(Object.getOwnPropertyDescriptor(person, 'name')); + // {value: "Lee", writable: true, enumerable: true, configurable: true} + ``` + + > **`Object.getOwnPropertyDescriptor(객체의 참조, 프로퍼티 키)`** 메서드 + > + > - 프로퍼티 디스크립터 객체 반환 + > - 만약 존재하지 않는 프로퍼티 or 상속받은 프로퍼티에 대해서 + > - 프로퍼티 디스크립터를 요구하면 undefined가 반환된다. + + ```jsx + const person = { + name: 'Lee' + }; + + // 프로퍼티 동적 생성 + 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} + } + */ + ``` + + > **`Object.getOwnPropertyDescriptors`** 메서드 + > + > - ES8 도입 + > - 모든 프로퍼티의 프로퍼티 디스크립터 객체들을 반환 + +### 데이터 프로퍼티와 접근자 프로퍼티 + +프로퍼티는 두 가지로 구분. + +1. 데이터 프로퍼티 + - 키와 값으로 구성된 일반적 프로퍼티 +2. 접근자 프로퍼티 + - 자체적으로는 값을 갖지 않고, 다른 데이터 프로퍼티의 값을 + 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티 + +1. 데이터 프로퍼티 + + ![image.png](image16_1.png) + + ```jsx + const person = { + name: 'Lee' + }; + + // 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 취득한다. + console.log(Object.getOwnPropertyDescriptor(person, 'name')); + // {value: "Lee", writable: true, enumerable: true, configurable: true} + ``` + + - 예제에서 알 수 있듯이, 프로퍼티가 생성될 때 + - [[Value]]의 값은 프로퍼티 값으로 초기화 + - 나머지는 true로 초기화 + - 프로퍼티를 동적 추가해도 동일하다. + + ```jsx + const person = { + name: 'Lee' + }; + + // 프로퍼티 동적 생성 + 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} + } + */ + ``` + +2. 접근자 프로퍼티 + + ![image.png](image16_2.png) + + ![image.png](image16_3.png) + + - 접근자 함수는 getter/setter 함수라고도 부른다. + + ```jsx + const person = { + // 데이터 프로퍼티 + firstName: 'Ungmo', + lastName: 'Lee', + + // 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); // Ungmo Lee + + // 접근자 프로퍼티를 통한 프로퍼티 값의 저장 + // 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다. + person.fullName = 'Heegun Lee'; + console.log(person); // {firstName: "Heegun", lastName: "Lee"} + + // 접근자 프로퍼티를 통한 프로퍼티 값의 참조 + // 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다. + console.log(person.fullName); // Heegun Lee + + // firstName은 데이터 프로퍼티다. + // 데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다. + let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName'); + console.log(descriptor); + // {value: "Heegun", 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} + ``` + + - firstName, lastName 프로퍼티 ⇒ 데이터 프로퍼티 + - getter, setter 함수의 fullName ⇒ 접근자 프로퍼티 + - 접근자 프로퍼티는 자체적으로 값을 가지지 않고 + - 데이터 프로퍼티의 값을 읽거나 저장할 때 관여 + +### 프로퍼티 정의 + + + +`Object.defineProperty` 메서드를 사용해서 프로퍼티 어트리뷰트 정의 + +`Object.defineProperties` 메서드를 사용해서 여러 개의 프로퍼티 한 번에 정의 + +```jsx +const person = {}; + +// 데이터 프로퍼티 정의 +Object.defineProperty(person, 'firstName', { + value: 'Ungmo', + writable: true, + enumerable: true, + configurable: true +}); + +Object.defineProperty(person, 'lastName', { + value: 'Lee' +}); + +let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName'); +console.log('firstName', descriptor); +// firstName {value: "Ungmo", writable: true, enumerable: true, configurable: true} + +// 디스크립터 객체의 프로퍼티를 누락시키면 undefined, false가 기본값이다. +descriptor = Object.getOwnPropertyDescriptor(person, 'lastName'); +console.log('lastName', descriptor); +// lastName {value: "Lee", writable: false, enumerable: false, configurable: false} + +// [[Enumerable]]의 값이 false인 경우 +// 해당 프로퍼티는 for...in 문이나 Object.keys 등으로 열거할 수 없다. +// lastName 프로퍼티는 [[Enumerable]]의 값이 false이므로 열거되지 않는다. +console.log(Object.keys(person)); // ["firstName"] + +// [[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: "Lee", 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 = 'Heegun Lee'; +console.log(person); // {firstName: "Heegun", lastName: "Lee"} +``` + +- Object.defineProperty 메서드로 프로퍼티를 정의할 때 일부 생략할 수 있다. + - 생략된 어트리뷰트는 기본값이 적용된다. + + ![image.png](image16_4.png) + + +### 객체 변경 방지 + +![image.png](image16_5.png) + +- 객체 확장 금지 + + > 프로퍼티 추가 금지 + > + - 확장 가능한 객체인지 여부 ? ⇒ `Object.isExtensible` 메서드로 확인 +- 객체 밀봉 + + > 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지 + > + > + > 밀봉된 객체는 읽기와 쓰기만 가능 + > + - 밀봉된 객체인지 여부? ⇒ `Object.isSealed` 메서드로 확인 +- 객체 동결 + + > 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지 + > + > + > 프로퍼티 값 갱신 금지. 동결된 객체는 읽기만 가능 + > + - 동결된 객체인지 여부? ⇒ `Object.isFrozen` 메서드로 확인 +- 불변 객체 + + > 직속 프로퍼티만 변경하는 것이 아니라, 중첩 객체까지 영향을 주는 메서드 + > + + 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 `Object.freeze` 메서드를 호출 \ No newline at end of file diff --git "a/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_1.png" "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_1.png" new file mode 100644 index 0000000..fb95b60 Binary files /dev/null and "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_1.png" differ diff --git "a/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_2.png" "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_2.png" new file mode 100644 index 0000000..4312e14 Binary files /dev/null and "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_2.png" differ diff --git "a/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_3.png" "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_3.png" new file mode 100644 index 0000000..ccd2de5 Binary files /dev/null and "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_3.png" differ diff --git "a/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_4.png" "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_4.png" new file mode 100644 index 0000000..ab29bb0 Binary files /dev/null and "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_4.png" differ diff --git "a/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_5.png" "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_5.png" new file mode 100644 index 0000000..df43d20 Binary files /dev/null and "b/SayisMe/ch16\355\224\204\353\241\234\355\215\274\355\213\260\354\226\264\355\212\270\353\246\254\353\267\260\355\212\270/image16_5.png" differ