코딩기록

JS)모던 자바스크립트 Deep Dive 16장 프로퍼티 어트리뷰트 본문

프론트/모던 자바스크립트 Deep Dive 책 스터디

JS)모던 자바스크립트 Deep Dive 16장 프로퍼티 어트리뷰트

뽀짝코딩 2024. 7. 19. 12:50
728x90

16장 프로퍼티 어트리뷰트

16-1. 내부 슬롯과 내부 메서드

  • 내부 슬롯과 내부 메서드란, 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티(pseudo property)와 의사 메서드(psuedo method). [덧, 의사 - 프로그램 모듈의 작동원리를 프로그램언어가 아닌 일반적인 언어로 사람이 알기 쉽게 작성한 것. 즉, 사양 명세서.]
    • 내부 슬롯과 내부 메서드는 ECMAScript 사양에 정의된 대로 구현되어 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아님. 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 제공.
    • 모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는데 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 원칙적으로 직접 접근할 수 없지만 [[Prototype]] 내부 슬롯의 경우, _ _proto_ _를 통해 간접적으로 접근 가능.
    •  
    •  
         const o = {};
         
         // 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 직접 접근불가.
         o.[[Prototype]] // -> Uncaught SyntaxError: Unexpected token '['
         // 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공.
         o.__proto__ // -> Object.prototype
    •    [예제 16-01]
덧),
es6 도입 이전에 browser들이 맘대로  __proto__ 만들어서 사용했음. 그래서 es6 버전에 만들어서 도입함.

__proto__ 가 현재 표준이긴 한데 없앨 예정. 언젠가 없앨 예정이라 Object.getPrototypeOf를 권장.
프로퍼티 확인(검색) - console.dir(프로퍼티이름적기);

 

 

16-2. 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체

  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의.
  • 프로퍼티 상태란 프로퍼티 값(value), 값의 갱신 가능 여부(writable), 열거 가능 여부(enumerable), 재정의 가능 여부(configurable)를 말함.
  • 프로퍼티 어트리뷰트란 자바스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부 슬롯 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 
    • ✨✨이 대괄호대괄호[[~~]] 이름들은 가상의 이름, ECMAScript에서 붙인 이름이고 이름보다는 이 내부 슬롯이 하는 역할이 더 중요하다.  (이름은 다를수 있다는 뜻) 
  •  
  • 객체 리터럴로 객체 하나생성함.
  • 펄슨이라는 식별자가 그 객체를 가리킴
  • name: 'Lee'프로퍼티가 하나 존재함
  • 객체리터럴은 할당문( = )이 동작하는 시점에 객체 리터럴{ ~~ }이 해석되어서 객체를 만듬. 그때 객체를 만듬과 동시에 프로퍼티들을 직접적으로 생성함 그때 프로퍼티 어트리뷰트들을 자동적으로 정의함.
  •  
    • 따라서 프로퍼티 어트리뷰트에 직접 접근할 수 없지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인할 수 있음. 
    •  
    •       [예제 16-02]
            const person = {
              name: 'Lee',
            };
           
            // 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다.
            console.log(Object.getOwnPropertyDescriptor(person, "name"));
            // { value: 'Lee', writable: true, enumerable: true, configurable: true }
    • getOwnPropertyDescriptor 메서드를 호출할 때 
      • 첫 번째 매개변수에는 객체의 참조를 전달하고, 두 번째 매개변수에는 프로퍼티 키문자열로 전달.
        • 이때 Object.getOwnPropertyDescriptor 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터(PropertyDescriptor)객체를 반환.
        • 존재하지 않거나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined가 반환.
        • Object.getOwnPropertyDescriptor 메서드하나의 프로퍼티에 대해 프로퍼티 디스크립터 객체를 반환하지만, ES8에서 도입된 Object.getOwnPropertyDescriptors 메서드모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환.
        •       [예제 16-03]
                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}
                 }
             */

 

16-3. 데이터 프로퍼티와 접근자 프로퍼티   

프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분.

 

1). 데이터 프로퍼티 (Data property)

  • 키와 값으로 구성된 일반적인 프로퍼티.
  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의.
  •   
    프로퍼티
    어트리뷰트
    프로퍼티 디스크립터
    객체의 프로퍼티
    설명
    [[Value]] value - 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값.
    - 프로퍼티 키를 통해 프로퍼티 값을 저장하면 [[Value]]에 값을 재할.
        이때 프로퍼티가 없으면 생성하고 생성된 프로퍼티의 [[value]]에 값을 저장.
    [[Writable]] writable - 프로퍼티 값의 변경 가능 여부를 나타내며 boolean 값을 가짐.
    - [[Writable]]의 값이 false인 경우,
         해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 됨.
    [[Enumerable]] enumerable - 프로퍼티의 열거 가능 여부를 나타내며 boolean 값을 가짐.
    - [[Enumerable]]의 값이 false인 경우,
        해당 프로퍼티는 for...in 문, Object.keys 메서드 등으로 열거 불가.
    [[Configurable]] configurable - 프로퍼티의 재정의 가능 여부를 나타내며 boolean 값을 가짐.
    - [[Configurable]]의 값이 false인 경우,
         해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경 금지.
    - [[Writable]]이 true인 경우,
         [[value]]와 [[Writable]]을 false로 변경하는 것은 허용.
    •       [예제 16-05]
            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}
             }
         */
  • 메서드가 반환된 프로퍼티 디스크립터 객체를 살펴보면 value 프로퍼티의 값은 'Lee'.
    • 프로퍼티 어트리뷰트 [[Value]]의 값이 'Lee'인 것을 의미.
  • writable, enumerable, configurable 프로퍼티의 값이 모두 true.
    • [[writable]], [[enumerable]], [[configurable]]의 값이 모두 true인 것을 의미.
      • 이처럼 프로퍼티가 생성될 때 [[Value]]의 값은 프로퍼티 값으로 초기화되며  [[writable]], [[enumerable]], [[configurable]]의 값은 true로 초기화됨. 프로퍼티를 동적으로 추가해도 마찬가지.
덧) 스터디에서 나온 이런저런 내용 240722월-

*데이터 프로퍼티는 꼭 알아야함.
데이터 프로퍼티는 프로퍼티 어트리뷰트 중 벨류가 있음.
접근자 프로퍼티 -> 밸류라는 프로퍼티가 없음. 즉, 다른 프로퍼티를 참조하거나 조작하는 역할을 하고 set, get이라는 함수 두개를 갖고 있음.

 

 

2). 접근자 프로퍼티 (accessor property)

  • 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수(accessor function)로 구성된 프로퍼티.

  • 프로퍼티
    어트리뷰트
    프로퍼티 디스크립터
    객체의 프로퍼티
    설명
    [[Get]] get - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값인 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환됨.
    [[Set]] set - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 어트리뷰트 [[Set]]의 값인 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장됨.
    [[Enumerable]] enumerable - 프로퍼티의 열거 가능 여부를 나타내며 boolean 값을 가짐.
    - [[Enumerable]]의 값이 false인 경우,
        해당 프로퍼티는 for...in 문, Object.keys 메서드 등으로 열거 불가.
    [[Configurable]] configurable - 프로퍼티의 재정의 가능 여부를 나타내며 boolean 값을 가짐.
    - [[Configurable]]의 값이 false인 경우,
         해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경 금지.
    - [[Writable]]이 true인 경우,
         [[value]]와 [[Writable]]을 false로 변경하는 것은 허용.
  • 접근자 함수는  getter/setter 함수라고도 부르며 접근자 프로퍼티는 getter와 setter 함수를 모두 정의할 수도 있고 하나만 정의할 수도 있음. 
  • getter, setter 함수
    • 메서드 앞에 get, set이 붙은 메서드 
  •  get  fullName()  {return`${this.firstName}${this.lastName}`; }  
    • 위 getter함수의 { } 안이 Get 프로퍼티 어트리뷰트의 값.
  •     set fullName(name)  { [this.firstNamethis.lastName= name.split(' '); }   
    • 위 setter함수의 { } 안이 Set 프로퍼티 어트리뷰트의 값.
  •   [예제 16-06]        
      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}
  •   [예제 16-06  코드 설명 
  •   // getter 함수   
  •    get  fullName()     getter는 참조할 때 호출됨.    
    •   console.log(person.fullName);      fullName에 접근하면 getter함수 호출됨. 
    • return 옆에 씌여진 `~~` 내부 로직이 자바스크립트에 의해 호출되고 그 결과 값이 반환됨.
    • getter인 경우엔 반드시 return 이 있어야 함. 프로퍼티를 참조하게 되면 무엇인가 값으로 평가되어야 하기 때문.
    •   const  person = {       this는 person 객체를 가리킴.
    • 두 개의 문자열을 조합해서 반환.
  •   // setter 함수    
  •    set  fullName(name)     setter는 값을 할당할 때 호출됨.
    •   person.fullName = 'Heegun Lee';     접근자 프로퍼티(fullName)에 값을 할당할때 setter 호출됨.
    •   set  fullName(name)  {     할당한 값이 매개변수(name)에 인수로 넘어가고 { ~~~ } 내부 로직을 실행함. (자체적으로 값을 갖고 있지 않으므로 다른 프로퍼티 값을 저장하는 행위를 함)
      •     [this.firstName, this.lastName] = name.split(' ');      스페이스바를 기준으로 문자를 나눠 배열로 리턴함. 첫 번째 요소('Heegun)가 firstName에 두 번째 요소(Lee')가 lastName에 할당됨.
    • [[Get]], [[Set]] - 다른 데이터 프로퍼티 값을 조작하여 가지고 오거나 저장할 때 조작할 수 있는 getter, setter 함수를 프로퍼티 어트리뷰트에 값으로 갖고 있음. (클래스, 생성자함수에서 다시 살펴볼 예정)
  • 접근자 프로퍼티는 자체적으로 값(프로퍼티 어트리뷰트 [[Value]])을 가지지 않으며 다만 데이터 프로퍼티의 값을 읽거나 저장할 때만 관여함.
덧) 스터디 질문 Q :
[[Get]], [[Set]] 접근자 프로퍼티 외에 함수로도 동작이 가능한데 왜이걸 사용하는지 궁금.
다른 학생답 => 인수가 하나일때 사용할 수 있다는 특징이 있음. 그럼 자바스크립트 엔진이 의도한대로 사용할 수 있다.

====강사님 답 A====
위 [예제16-06]  함수를 내부에 감추겠다는 의도. 사용자를 더 배려한 행위.
const person = {
  // 데이터 프로퍼티
  firstName: 'Ungmo',
  lastName: 'Lee',

  // getter 함수  
  get fullName() {    // fullName은 접근자 함수로 구성된 접근자 프로퍼티.
    return `${this.firstName} ${this.lastName}`;
  },
  // setter 함수
  set fullName(name) {

    [this.firstName, this.lastName] = name.split(' ');
  }
};

console.log(person.fullName);   // Ungmo Lee

person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}
console.log(person.fullName);   // Heegun Lee



fullName() 이라는 접근자 프로퍼티는 getter라는 함수로 구현이 되어있지만 사용할 때는 프로퍼티로 써야된다.
person.fullName을 참조했을 때 뭔가 값이 나와야 함. 즉, get함수는 반드시 리턴해야함.

set은 프로퍼티 처럼 사용.
person.fullName = 'Heegun Lee';
'Heegun Lee'  이라는 문자열이  fullName 이라는 프로퍼티를 갱신하기위해준 것처럼 쓰지만
실제로 'Heegun Lee' 이 값이 setter의 (name)이라는 인수로 넘어간다.
따라서 setter함수는 인수를 단 1개만 받을 수 있고 뮤테이션(값 변경)을 해야함.

[함수로 씀] 개발자를 배려한 행위.
const person = {
  // 데이터 프로퍼티
  firstName: 'Ungmo',
  lastName: 'Lee',

  // getter 함수
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  },

  // setter 함수
  setFullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
};


console.log(person.getFullName());  // Ungmo Lee

console.log(person.setFullName('Heegun Lee'));  
console.log(person.getFullName());  // Heegun Lee

 


둘다 된다라는 측면에서는 똑같으나 (~~~ 기억안남~~~~)
현업에서 접근자 프로퍼티를 많이 안쓰는데 접근자 프로퍼티의 선기능을 몰라서 많이 안쓴다는 생각이 듬.
JS에서 제공하는 빌트인 프로퍼티 메서드를 많이, 잘 알고 있는 것이 구현능력의 상승과 밀접한 연관이 있음.
Dom에도 굉장히 다양한 프로퍼티들이 제공됨. 접근자 프로퍼티로 해결가능하면 함수보단 접근자 프로퍼티로 해결하려는 흔적이 많음. 


✅스터디 내용을 기억에 의존해서 쓴 글이라 틀린 부분이 있다면 댓글 부탁드립니다.- 뽀짝코딩 주인장-

 

16-4. 프로퍼티 정의   

  • 프로퍼티 정의란 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것.
    • Object.defineProperty 메서드
      • 프로퍼티의 어트리뷰트를 정의 가능.
      • 인수로는 객체의 참조와 데이터 프로퍼티의 키인 문자열, 프로퍼티 디스크립터 객체를 전달함. 
      • Object.defineProperty 메서드로 프로퍼티를 정의할 때 프로퍼티 디스크립터 객체의 프로퍼티를 일부 생략 가능.
      • Object.defineProperty 메서드는 한번에 하나의 프로퍼티만 정의 가능.
      • Object.defineProperties 메서드는 여러 개의 프로퍼티를 한 번에 정의 가능.
  • [ Object.defineProperty 메서드 ]
  •  
  •  
       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"}
  •    [예제 16-08]

 

프로퍼티 디스크립터 객체의 프로퍼티 대응하는 프로퍼티 어트리뷰트 생략했을 때의 기본값
value [[Value]] undefined
get [[Get]] undefined
set [[Set]] undefined
writable [[writable]] false
enumerable [[Enumerable]] false
configurable [[Configurable]] false

 

 

  • [ Object.defineProperties 메서드 ]
  •    [예제 16-09] 
       const person = {};
       
       Object.defineProperties(person, {
         // 데이터 프로퍼티 정의
         firstName: {
           value: 'Ungmo',
           writable: true,
           enumerable: true,
           configurable: true
         },
         lastName: {
           value: 'Lee',
           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 = 'Heegun Lee';
       console.log(person); // {firstName: "Heegun", lastName: "Lee"}

 

 

16-5. 객체 변경 방지   

  • 객체는 변경이 가능한 값이므로 재할당 없이 직접 변경 가능.
    • 프로퍼티를 추가하거나 삭제 가능.
    • 프로퍼티 값을 갱신 가능.
    • Object.defineProperty 또는 Object.defineProperties 메서드를 사용하여 프로퍼티 어트리뷰트를 재정의 가능.
  • 자바스크립트는 객체의 변경을 방지하기 위해 다양한 메서드를 제공.
  • 객체 변경 방지 메서드들은 객체의 변경을 금지하는 강도가 다름.
구분 메서드 상태 확인
메서드
프로퍼티
추가
프로퍼티
삭제
프로터피
값 읽기
프로퍼티
값 쓰기
프로퍼티
어트리뷰트 재정의
객체 금지 Object.preventExtensions Object.isExtensible X O O O O
객체 밀봉 Object.seal isSealed X X O O X
객체 동결 Object.freeze isFrozen X X O X X

 

 

1). 객체 확장 금지

  • Object.preventExtensions 메서드는 객체의 확장을 금지.
  • 확장이 금지된 객체는 프로퍼티 추가가 금지.
  • 프로퍼티를 추가할 수 있는 2가지 방법 모두 추가 금지.
    • ①프로퍼티 동적 추가와 ②Object.defineProperty 메서드.
  • 확장 가능 객체 여부 확인 메서드-  Object.isExtensible 
  •    [예제 16-10]  
       const person = { name: 'Lee' };
       
       // person 객체는 확장이 금지된 객체가 아님.
       console.log(Object.isExtensible(person)); // true
       
       // person 객체의 확장을 금지하여 프로퍼티 추가를 금지.
       Object.preventExtensions(person);
       
       // person 객체는 확장이 금지된 객체.
       console.log(Object.isExtensible(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: Cannot define property age, object is not extensible

 

 

2). 객체 밀봉

  • Object.seal 메서드는객체를 밀봉.
  • 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지 의미.
    • 즉, 밀봉된 객체는 읽기와 쓰기만 가능.
  • 밀봉된 객체 여부 확인 메서드-  Object.isSealed  
  •  
       [예제 16-11]
       const person = { name: 'Lee' };
       
       // person 객체는 밀봉(seal)된 객체가 아님.
       console.log(Object.isSealed(person)); // false
       
       // person 객체를 밀봉(seal)하여 프로퍼티 추가, 삭제, 재정의를 금지.
       Object.seal(person);
       
       // person 객체는 밀봉(seal)된 객체.
       console.log(Object.isSealed(person)); // true
       
       // 밀봉(seal)된 객체는 configurable이 false.
       console.log(Object.getOwnPropertyDescriptors(person));
       /*
       {
         name: {value: "Lee", writable: true, enumerable: true, configurable: false},
       }
       */
       
       // 프로퍼티 추가가 금지.
       person.age = 20; // 무시. strict mode에서는 에러
       console.log(person); // {name: "Lee"}
       
       // 프로퍼티 삭제가 금지.
       delete person.name; // 무시. strict mode에서는 에러
       console.log(person); // {name: "Lee"}
       
       // 프로퍼티 값 갱신은 가능.
       person.name = 'Kim';
       console.log(person); // {name: "Kim"}
       
       // 프로퍼티 어트리뷰트 재정의가 금지.
       Object.defineProperty(person, 'name', { configurable: true });
       // TypeError: Cannot redefine property: name

 

 

3). 객체 동결

  • Object.freeze 메서드는 객체를 동결.
  • 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지 의미.
    • 즉, 동결된 객체는 읽기만 가능.
  • 동결된 객체 여부 확인 메서드-  Object.isFrozen 
  •  
  •    [예제 16-12]
       const person = { name: 'Lee' };
       
       // person 객체는 동결(freeze)된 객체가 아님.
       console.log(Object.isFrozen(person)); // false
       
       // person 객체를 동결(freeze)하여 프로퍼티 추가, 삭제, 재정의, 쓰기를 금지.
       Object.freeze(person);
       
       // person 객체는 동결(freeze)된 객체.
       console.log(Object.isFrozen(person)); // true
       
       // 동결(freeze)된 객체는 writable과 configurable이 false.
       console.log(Object.getOwnPropertyDescriptors(person));
       /*
       {
         name: {value: "Lee", writable: false, enumerable: true, configurable: false},
       }
       */
       
       // 프로퍼티 추가가 금지.
       person.age = 20; // 무시. strict mode에서는 에러
       console.log(person); // {name: "Lee"}
       
       // 프로퍼티 삭제가 금지.
       delete person.name; // 무시. strict mode에서는 에러
       console.log(person); // {name: "Lee"}
       
       // 프로퍼티 값 갱신이 금지.
       person.name = 'Kim'; // 무시. strict mode에서는 에러
       console.log(person); // {name: "Lee"}
       
       // 프로퍼티 어트리뷰트 재정의가 금지.
       Object.defineProperty(person, 'name', { configurable: true });
       // TypeError: Cannot redefine property: name
덧) 스터디에서 나온 이런저런 내용 240722월-

*객체 동결- 프로퍼티를 추가,삭제,갱신,쓰기,재정의 못하고 읽기만함.
쓰는이유는 객체내부가 중첩되고 중첩이될 가능성이 있어서임.
안까지 싹 얼리려면 재귀적행위를 해야하는데 객체가 크면 굉장히 부담스러움.
메모리를 많이 사용하는 퍼포먼스 때문에 많이 사용하진 않음.

 

 

4). 불변 객체

  • 지금까지 살펴본 변경 방지 메서드들은 얕은 변경 방지(shallow only)로 직속 프로퍼티만 변경이 방지되고 중첩 객체까지는 영향을 주지는 못함.
    • 따라서 Object.freeze 메서드로 객체를 동결하여도 중첩 객체까지 동결할 수 없음.
    •    [예제 16-13]
         const person = {
           name: 'Lee',
           address: { city: 'Seoul' }
         };
         
         // 얕은 객체 동결
         Object.freeze(person);
         
         // 직속 프로퍼티만 동결.
         console.log(Object.isFrozen(person)); // true
         // 중첩 객체까지 동결하지 못함.
         console.log(Object.isFrozen(person.address)); // false
         
         person.address.city = 'Busan';
         console.log(person); // {name: "Lee", address: {city: "Busan"}}
  • 객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드 호출해야함.
  •    [예제 16-14]
       function deepFreeze(target) {
         // 객체가 아니거나 동결된 객체는 무시하고 객체이고 동결되지 않은 객체만 동결.
         if (target && typeof target === 'object' && !Object.isFrozen(target)) {
           Object.freeze(target);
           /*
             모든 프로퍼티를 순회하며 재귀적으로 동결.
             Object.keys 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환.
             ("19.15.2. Object.keys/values/entries 메서드" 참고)
             forEach 메서드는 배열을 순회하며 배열의 각 요소에 대하여 콜백 함수를 실행.
             ("27.9.2. Array.prototype.forEach" 참고)
           */
           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(Object.isFrozen(person.address)); // true
       
       person.address.city = 'Busan';
       console.log(person); // {name: "Lee", address: {city: "Seoul"}}

 

 

 

 

 

 

 

참고

도서 - 모던 자바스크립트 Deep Dive -이웅모

이웅모 강사님 홈피 https://poiemaweb.com/js-prototype

이웅모 강사님 유튜브  -  https://www.youtube.com/watch?v=0AjTZG6bGq8

블로그 - https://codeno-te.tistory.com/152

 

✅ 덧, 부분은 스터디 내용을 기억에 의존해서 쓴 글이라 틀린 부분이 있다면 댓글 부탁드립니다.- 뽀짝코딩 주인장-

 

반응형
Comments