[모던 자바스크립트 Deep Dive] 16. 프로퍼티와 어트리뷰트

5 minute read

모던 딥다이브

🎉 프로퍼티, 어트리뷰트

😉 내부 슬롯과 내부 메서드

  • ECMAScript 사양에 등장하는 이중 대괄호[[…]]로 감싼 이름인 내부 슬롯과 내부 메서드는 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아니다.
  • 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공한다.

    • 예를 들어, 모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다.
    • [[Prototype]] 내부 슬롯의 경우, __proto__를 통해 간접적으로 접근할 수 있다.
const o = {};

o.[[Prototype]] // Uncaught SyntaxError: Unexpected token '['
o.**proto** // Object.prototype

😉 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체

  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
  • 프로퍼티 어트리뷰트 : 자바스크립트 엔진이 관리하는 내부 상태 값 인 내부 슬롯
    • 프로퍼티의 값 [[Value]]
    • 값을 갱신 가능 여부 [[Writable]]
    • 열거 가능 여부 [[Enumerable]]
    • 재정의 가능 여부 [[Configurable]]
  • Object.getOwnPropertyDescriptor 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다. 만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요규하면 undefined가 반환된다.
const person = {
  name: 'Lee'
};

// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다.
console.log(Object.getOwnPropertyDescriptor(person, 'name');
// {value: 'Lee', writable: true, enumerable: true, configurable: true}

😉 데이터 프로퍼티와 접근자 프로퍼티

🐱‍🐉 데이터 프로퍼티

키와 값으로 구성된 일반적인 프로퍼티

데이터 프로퍼티의 프로퍼티 어트리뷰트

1] [[Value]]

  • 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값
  • 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당한다. 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값을 지정한다.

2] [[Writable]]

  • 프로퍼티 값의 변경 기능 여부를 나타내며 불리언 값을 갖는다.
  • [[Writable]]값이 false인 경우, 해당 프로퍼티 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다.

3] [[Enumerable]]

  • 프로퍼티 열거 기능 여부를 나타내며 불리언 값을 갖는다.
  • [[Enumerable]]의 값이 false인 경우 해당 프로퍼티는 for..in문이나 Object.keys메서드 등으로 열거 할 수 없다.

4] [[Configurable]]

  • 프로퍼티의 재정의(다시정의) 가능 여부를 나타내며 불리언 값을 갖는다.
  • [[Configurable]]의 값이 false인 경우 해당 프로퍼티의 삭제,어트리뷰트 값의 변경이 금지 된다. 단, [[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]은 false로 변경하는 것은 허용된다.

프로퍼티가 생성될 때 [[Value]]의 값은 프로퍼티 값으로 초기화되며 [[Writable]], [[Enumerable]], [[Configurable]]의 값이 true로 초기화된다.

🐱‍🐉 접근자 프로퍼티

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

접근자 프로퍼티의 프로퍼티 어트리뷰트

1] [[Get]]

  • 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수
  • 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 그 결과가 프로퍼티 값을 반환한다.
  • return이 꼭 있어야 한다.

2] [[Set]]

  • 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 지정할 때 호출되는 접근자 함수
  • 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉 setter 함수가 호출되고 그 결과 프로퍼티 값으로 저장된다.

3] [Enumerable]] 데이터 프로퍼티와 동일하다.

4] [[Configurable]] 데이터 프로퍼티와 동일하다.

const person = {
  //데이터 프로퍼티
  firstName = 'Ungmo',
  lastName = 'Lee',

  //접근자 프로퍼티
  //getter함수
  get fullName(){
    return `${this.firstName} ${this.lastName}`;
  },
  //setter함수
  set fullName(){
    [this.firstName, this.lastName] = name.split(' ');
  }
};

1] 데이터 프로퍼티를 통한 프로퍼티 값 참조

consoel.log(person.firstName + " " + person.lastName);
//Ungmo Lee

2] 접근자 프로퍼티를 통한 프로퍼티 값의 저장

  • 접근자 프로퍼티 fullName에 값을 저장하면 setter함수가 호출된다.
person.fullName = "Heegun Lee";
console.log(person);
//{firstName : 'Heegun', lastName : 'Lee'}

3] 접근자 프로퍼티를 통한 프로퍼티 값의 참조

  • 접근자 프로퍼티 fullName에 접근하면, getter 함수가 호출된다.
console.log(person.fullName);
//Heegun Lee

4] 접근자 프로퍼티의 어트리뷰트 출력

  • [[Get]] [[Set]] [[Enumerable]] [[Configurable]]을 가지고 있다.
let descriptor = Object.getOwnPropertyDescriptor(person, "fullName");
console.log(descriptor);
//{get : f, set : f, enumerable : true, configurable : true}

🐱‍🐉 프로토타입(Prototype)

어떤 객체의 상위(부모) 객체의 역활을 하는 객체

  • 하위(자식)객체에게 자신의 프로퍼티와 메서드를 상속한다.
  • 상속 받은 하위 객체는 자신의 프로퍼티 또는 메서드의 것처럼 자유롭게 사용할 수 있다.
  • 프로토타입 체인은 프로토타입이 단반향 링크드 리스트 형태로 연결되어 있는 상속 구조이다.
  • 객체의 프로퍼티나 메서드에 접근하려고 할때 해당 객체에 접근하려는 프로퍼티 또는 메서드가 없다면 프로토타입 체인을 따라 프로토타입의 프로토타입의 프로퍼티나 메서드를 차례로 검색한다.

😉 프로퍼티 정의

  • 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의
  • 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것 → Object.defineProperty와 Object.definePropertes 메서드 사용
    • Object.defineProperty : 한번에 하나의 프로퍼티만 정의
    • Object.definePropertes : 여러개의 프로퍼티를 한 번에 정의
const person ={};

//데이터 프로퍼티 정의
Object.defineProperty(person.'firstName',{
	value : 'Ungmo',
    writable : true,
    enumerable : true,
    configurable : true
});

Object.defineProperty(person. 'lastName', {
     vaule: 'Lee'
});

//접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName',{
  get(){
    return `${this.firstName}, ${this.lastName}`;
  },
  set(name){
    [this.firstName, this.lastName] = name.split(' ');
  },
  enumerable : true,
  configurable : true
});
  • [[writable]] [[enumerable]] [[configurable]] 생략 가능
  • 디스크립터 객체의 프로퍼티를 누락 시 기본값
    1. value, get, set : undefined
    2. wirtable, enumerable, configurable : false

😉 객체 변경 방지

🐱‍🐉 객체 확장 금지

확장이 금지된 객체는 프로퍼티 추가가 금지된다. (동적 추가, Object.defineProperty메서드)

  • 메서드 : Object.preventExtensions
  • 확장 확인 메서드 : Object.isExtensible
const person = { name: "Lee" };
//person 객체는 확장이 금지된 객체가 아니다.
console.log(Object.isExtension(person)); //true

//person 객체의 확장을 금지하여 프로퍼티 추가를 금지한다.
Object.preventExtension(person);

//person 객체는 확장이 금지된 객체이다.
console.log(Object.isExtension(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 : Cannnot define property age, object is not extensible

🐱‍🐉 객체 밀봉

Object.seal 메서드

밀봉된 객체는 읽기와 쓰기만 가능하다.

  • 추가, 삭제, 재정의를 금지
  • Object.isSealed 메서드로 밀봉 객체 여부 확인 가능
const person = { name: "Lee" };
//person 객체는 밀봉된 객체가 아니다.
console.log(Object.isSealed(person)); //false

//person 객체의 밀봉하여 프로퍼티 추가, 삭제, 재정의를 금지한다.
Object.Seal(person);

//person 객체는 밀봉된 객체이다.
console.log(Object.isSealed(person)); //true

//밀봉된 객체는 configurable이 false이다.
console.log(Object.getOwnPropertyDescriptor(person));
/*{name : {value : 'Lee', wirtable : true, enumerable : true, configurable : false}*/
  • 프로퍼티 추가가 금지된다.
  • 프로퍼티 삭제가 금지된다.
delete.person.name;
//무시. strict mode에서는 에러
console.log(person); //{name : 'Lee'}
  • 프로퍼티 값 갱신은 가능하다.
person.name = "Kiem";
console.log(person); //{name : 'Kim'}
  • 프로퍼티 어트리뷰트 재정의가 금지된다.
Object.defineProperty(person, "name", { configurable: true });
//TypeError : Cannnot redefine propery : name

🐱‍🐉 객체 동결

Object.freeze 메서드

동결된 객체는 읽기만 가능하다.

  • 프로퍼티 추가, 삭제, 재정의, 쓰기 금지
  • Objct.isFrozen 메서드 동결 객체인지 여부 확인 가능
const person = { name: "Lee" };
//person 객체는 동결된 객체가 아니다.
console.log(Object.isFrozne(person)); //false

//person 객체의 동결하여 프로퍼티 추가, 삭제, 재정의, 쓰기가 금지한다.
Object.Freeze(person);

//person 객체는 동결된 객체이다.
console.log(Object.isFrozne(person)); //true
//밀봉된 객체는 Writable이 false이다.
console.log(Object.getOwnPropertyDescriptor(person));
/*{name : {value : 'Lee', wirtable : false, enumerable : true, configurable : false}*/
  • 프로퍼티 추가가 금지된다.

  • 프로퍼티 삭제가 금지된다.

  • 프로퍼티 값 갱신이 금지된다.

person.name = "Kim";
//무시, strict mode에서 에러
console.log(person);
//{name : 'Lee'}
  • 프로퍼티 어트리뷰트 재정의가 금지된다.

🐱‍🐉 불변 객체

객체 확장 금지, 객체 밀봉, 객체 동결은 얕은 변경 방식으로 중첩 객체까지 동결 할 수 없다. 따라서, 재귀적으로 Object.freeze 메서드를 호출해야 한다.

fucntion deepFreeze(target){
//객체가 아니거나 동결된 객체는 무시하고 객체이고 동결되지 않은 객체는 동결한다.
  if(target && typeof target === 'object' && !Object.isFrozen(target)){
    Object.Freeze(target);
    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(Objdec.isFrozen(person.address)); //true

person.address.city = 'Busan';
console.log(person.address);
//city : 'Seoul'

Leave a comment