JS (16) - 프로퍼티 어트리뷰트

최조니·2022년 7월 4일
0

JavaScript

목록 보기
13/36

16.1 내부 슬롯과 내부 메서드

내부 슬롯, 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티의사 메서드

  • 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아님

  • 모든 객체는 [[prototype]] 라는 내부 슬롯을 갖지만 접근할 수 없음
    __proto__ 를 통해 간접적 접근 가능

const o = {};

o.[[prototype]]	// Uncaught SyntaxError: Unexpected token '['
o.__proto__		// Object.prototype

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

  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본 값으로 자동 정의

  • 프로퍼티의 상태

    • 프로퍼티의 값 (value)
    • 값의 갱신 가능 여부 (writable)
    • 열거 가능 여부 (enumerable)
    • 재정의 가능 여부 (configurable)
  • Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인 가능

    • 첫 번째 매개변수에는 객체의 참조 전달
    • 두 번째 매개변수에는 프로퍼티 키를 문자열로 전달
    • 반환 값 : 프로퍼티 디스크립터
const person = {
  name: 'Lee'
};

console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {vale: "Lee", writable: true, enumerable: true, configurable: true}

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

  • 데이터 프로퍼티
    : 키와 값으로 구성된 일반적인 프로퍼티

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

- 데이터 프로퍼티

프로퍼티 어트리뷰트프로퍼티 디스크립터 객체의 프로퍼티
[[Value]]value
[[Writable]]writable
[[Enumerable]]enumerable
[[Configurable]]configurable
  • [[Value]] - value
    - 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값
    - 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당
    - 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]] 값을 저장

  • [[Writable]] - writable

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

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

  • [[Configurable]] - configurable
    • 프로퍼티 재정의 가능 여부를 나타내며 불리언 값을 가짐
    • [[Configurable]]의 값이 false인 경우 해당 프로퍼티의 삭제, 값 변경이 금지됨,
      단, [[Writable]]의 값이 true인 경우 [[Value]], [[Writable]]을 false로 변경하는 것은 허용
const person = {
  name: 'Lee'
};

console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {vale: "Lee", writable: true, enumerable: true, configurable: true}
const person = {
  name: 'Lee'
};

person.age = 20;

console.log(Object.getOwnPropertyDescriptor(person));
/* 
name: {vale: "Lee", writable: true, enumerable: true, configurable: true},
age: {vale: 20, writable: true, enumerable: true, configurable: true}
*/

- 접근자 프로퍼티

프로퍼티 어트리뷰트프로퍼티 디스크립터 객체의 프로퍼티
[[Get]]get
[[Set]]set
[[Enumerable]]enumerable
[[Configurable]]configurable
  • [[Get]] - get
    - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수
    - 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 프로퍼티 값이 반환됨


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

  • [[Enumerable]] - enumerable
    • 데이터 프로퍼티의 [[Enumerable]]과 동일

  • [[Configurable]] - configurable
    • 데이터 프로퍼티의 [[Configurable]]과 동일
const person = {
  firstName: 'Ungmo',
  lastName: 'Lee',
  
  // getter 함수
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  
  // setter 함수
  set fullName(name) {
    [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은 데이터 프로퍼티
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);	// {value: "Heegun", writable: true, enumerable: true, configurable: true}

// fullName은 접근자 프로퍼티
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);	// {get: f, set: f, enumerable: true, configurable: true}
  • ❓ 접근자 프로퍼티 fullName으로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메서드가 호출되어 다음과 같이 동작
    • 프로퍼티 키가 유효한지 확인 (프로퍼티 키는 문자열 또는 심벌이어야 함)
    • 프로토타입 체인에서 프로퍼티 검색 (person 객체에서 fullName 프로퍼티 존재)
    • 검색된 fullName 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인
    • 접근자 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]] getter 함수를 호출하여 그 결과 반환

16.4 프로퍼티 정의

프로퍼티 정의란❓
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것

  • Object.defineProperty 메서드를 사용하여 프로퍼티의 어트리뷰트 정의
const person = {};

Object.defineProperty(person, 'firstName', {
  value: 'Ungmo',
  writable: true,
  enumerable: true,
  configurable: true
});

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

let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log('firstName', descriptor);

descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);

console.log(Object.keys(person));

person.lastName = 'Kim';
delete person.lastName;

descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);

Object.defineProperty(person, 'fullName', {
  get() {
    return `${this.firstName} ${this.lastName}`;
  },
  
  set(name) {
    [this.firstName, this.lastName] = name.split(' ');
  },
  enumerable: true,
  configurable: true
});

descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log('fullName', descriptor);

person.fullName = "Heegun Lee";
console.log(person);
프로퍼티 디스크립터 객체의 프로퍼티대응하는 프로퍼티 어트리뷰트생략했을 때의 기본 값
value[[Value]]undefined
get[[Get]]undefined
set[[Set]]undefined
writable[[Writable]]false
enumerable[[Enumerable]]false
configurable[[Configurable]]false

  • Object.defineProperty 메서드는 한번에 하나의 프로퍼티만 정의

  • Object.defineProperties 메서드를 사용하면 여러 개의 프로퍼티를 한 번에 정의할 수 있음

const person = {};

Object.defineProperties(person, {
  firstName: {
    value: 'Ungmo',
    writable: true,
    enumerable: true,
    configurable: true
  },
  lastName: {
    value: 'Lee',
    writable: true,
    enumerable: true,
    configurable: true
  },
  fullName: {
    get() {
      return `${this.firstName} ${this.lastName}`;
    },
    set(name) {
      [this.firstName, this.lastName] = name.split(' ');
    },
    enumerable: true,
    configurable: true
  }
});

person.fullName = "Heegun Lee";
console.log(person);

16.5 객체 변경 방지

  • 객체는 변경 가능한 값이므로 재할당 없이 직접 변경 가능

  • 즉, 프로퍼티를 추가, 삭제, 값 갱신 가능

  • Object.defineProperty , Object.defineProperties 메서드를 사용하여 프로퍼티 어트리뷰트 재정의 가능

  • 자바스크립트는 객체의 변경을 방지하는 다양한 메서드 제공

구분메서드추가삭제값 읽기값 쓰기어트리뷰트 재정의
객체 확장 금지Object.preventExtensionsXOOOO
객체 밀봉Object.sealXXOOX
객체 동결Object.freezeXXOXX

- 객체 확장 금지

Object.preventExtensions 메서드는 객체의 확장을 금지
→ 확장이 금지된 객체는 프로퍼티 추가가 금지됨

const person = { name: 'Lee' };	

console.log(Object.isExtensible(person));	// true

// 객체 확장 금지
Object.preventExtensions(person);	

console.log(Object.isExtensible(person));	// false

person.age = 20;	// 객체 확장이 금지되어 있으므로 무시됨
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

- 객체 밀봉

Object.seal 메서드는 객체를 밀봉
객체 밀봉 : 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지
→ 밀봉된 객체는 읽기와 쓰기만 가능

const person = { name: 'Lee' };

console.log(Object.isSealed(person));	// false

// 객체 밀봉 -> 프로퍼티 추가, 삭제, 재정의 금지
Object.seal(person);

console.log(Object.isSealed(person));	// true

console.log(Object.getOwnPropertyDescriptors(person));

person.age = 20;
console.log(person);

delete person.name;
console.log(person);

person.name = 'Kim';
console.log(person);

Object.defineProperty(person, 'name', { configurable: true });

- 객체 동결

Object.freeze 메서드는 객체를 동결
객체 동결 : 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지
→ 동결된 객체는 읽기만 가능

const person = { name: 'Lee' };

console.log(Object.isFrozen(person));

Object.freeze(person);

console.log(Object.isFrozen(person));

console.log(Object.getOwnPropertyDescriptors(person));

person.age = 20;
console.log(person);

delete person.name;
console.log(person);

person.name = 'Kim';
console.log(person);

Object.defineProperty(person, 'name', { configurable: true });

- 불변 객체

객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야 함

const person = {
  name: 'Lee',
  address: { city: 'Seoul' }
};

Object.freeze(person);

console.log(Object.isFrozen(person));
console.log(Object.isFrozen(person.address));

person.address.city = 'Busan';
console.log(person);
function 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));
console.log(Object.isFrozen(person.address));

person.address.city = 'Busan';
console.log(person);
profile
Hello zoni-World ! (◍ᐡ₃ᐡ◍)

0개의 댓글