fffo

프로퍼티 어트리뷰트 본문

Programming/Javascript

프로퍼티 어트리뷰트

gggs 2021. 9. 30. 20:41

프로퍼티 어트리뷰트

내부 슬롯과 내부 메서드

  • js 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사(pseudo) 프로퍼티와 의사 메서드임
  • 이중 대괄호[[...]]로 감싸서 표현
  • js엔진에서 실제로 동작하지만 개발자가 직접 접근할 수는 없음 → 간접적 접근이 가능한 일부 내부 슬롯과 내부 메서드 존재 ([[Prototype]]을 proto를 통해 접근 가능)

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

  • js 엔진은 프로퍼티를 생성할 때 프로퍼티 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의함
  • 프로퍼티 어트리뷰트는 js 엔진이 관리하는 내부 상태 값(meta-property)인 내부 슬롯들로 이루어짐
  • 프로퍼티 어트리뷰트에 직접 접근할 수 없지만 Object.getOwnPropertyDescriptor 메서드를 이용해 간접적으로 확인 가능

Object.getOwnPropertyDescriptor() 메서드

  • 첫 번째 매개변수에 객체의 참조 전달, 두 번째 매개변수에 프로퍼티 키를 문자열로 전달
  • 프로퍼티 디스크립터 객체를 반환
  • ES8에서 도입된 Object.getOwnPropertyDescriptors 메서드(메서드 이름에 s가 추가됨)는 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환함
const person = {
  name: 'dlwlrma'
};

person.age = 29;

console.log(Object.getOwnPropertyDescriptor(person, "age"));
// { value: 29, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptors(person));
// { 
//   name: { value: 'dlwlrma', writable: true, enumerable: true, configurable: true },
//   age: { value: 29, writable: true, enumerable: true, configurable: true }
// }

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

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

데이터 프로퍼티

  • 데이터 프로퍼티는 다음의 프로퍼티 어트리뷰트를 가짐
    • [[Value]] : 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값
    • [[Writable]] : 값의 갱신 가능 여부를 나타내는 불리언 값
    • [[Enumerable]] : 열거 가능 여부를 나타내는 불리언 값
      • false일 경우 해당 프로퍼티는 for ... in 문이나 Object.keys 메서드 등으로 열거할 수 없음
    • [[Configurable]] : 재정의 가능 여부를 나타내는 불리언 값
      • false일 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경 금지
      • 단, [[Writable]]이 true일 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용됨
  • 프로퍼티가 생성될 때 [[Value]]의 값은 프로퍼티 값으로, 나머지는 모두 true로 초기화됨

접근자 프로퍼티

  • 접근자 프로퍼티는 다음의 프로퍼티 어트리뷰트를 가짐
    • [[Get]] : 접근자 프로퍼티 키로 프로퍼티 값에 접근 시 프로퍼티 어트리뷰트 [[Get]]의 값인 getter함수가 호출됨
    • [[Set]] : 접근자 프로퍼티 키로 프로퍼티 값을 저장 시 프로퍼티 어트리뷰트 [[Set]]의 값인 setter함수가 호출됨
    • [[Enumerable]] : 데이터 프로퍼티와 같음
    • [[Configurable]] : 데이터 프로퍼티와 같음
const person = {
  firstName: 'wlrma',
  lastName: 'dl',
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  },
};

// console.log(person.fullName());  괄호를 붙이면 안됨
console.log(person.fullName); // wlrma dl
person.fullName = "sanghun moon";
console.log(person.firstName); // sanhun
console.log(person.fullName); // sanhun moon

console.log(Object.getOwnPropertyDescriptor(person, "fullName"));
// { get: [Function: get fullName],
//   set: [Function: set fullName],
//   enumerable: true,
//   configurable: true
// }
  • 위의 코드에서
    • firstName, lastName은 데이터 프로퍼티
    • fullName은 접근자 프로퍼티

프로퍼티 정의

  • 새로운 프로퍼티를 추가할 때 프로퍼티 어트리뷰트를 명시정으로 정의하거나 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의 → 객체의 프로퍼티가 어떻게 동작하는지 명확히 정의 가능

Object.defineProperty

  • 프로퍼티를 정의하는 메서드
  • 객체 참조, 프로퍼티 키 문자열, 프로퍼티 디스크립터 객체 순으로 인자 전달
const { first, set } = require("lodash");

const person = {};
Object.defineProperty(person,'firstName', {
  value: "wlrma",
  writable: true,
  enumerable: true,
  configurable: true, 
});

Object.defineProperty(person,"lastName", {
  value: "dl",
})

let descriptor = Object.getOwnPropertyDescriptor(person,"lastName");
console.log(descriptor);
// {
//   value: 'dl',
//   writable: false,
//   enumerable: false,
//   configurable: false
// }

person.lastName = "wj"; //무시됨
console.log(person.lastName); // dl

console.log(Object.keys(person)); // [ 'firstName' ]

Object.defineProperty(person, 'fullName', {
  get() {
    return `${firstName} ${lastName}`;
  },
  set(name) {
    [this.firstName, this.lastName] = name.split(' ');
  },
  enumerable: true,
  configurable: true,
});
  • writable 이 false값일 때 value를 재할당 시 error가 아닌 무시됨을 주의
  • Object.defineProperty 메서드에서 각 프로퍼티 어트리뷰트의 기본 값
    • value : undefined
    • get : undefined
    • set : undefined
    • writable : false
    • enumerable : false
    • configurable : false
  • Object.defineProperties 메서드로 2개 이상의 프로퍼티를 한 번에 정의할 수 있음

객체 변경 방지

  • 객체는 변경 가능한 값임. 이에 따라 변경을 방지하는 방법도 존재

객체 확장 금지

  • Object.preventExtensions( {객체} ) : 프로퍼티 추가가 금지됨
    • 프로퍼티 동적 추가 금지, Object.defineProperty 메서드 금지
  • Object.isExtensible 메서드 : 객체 확장 가능 여부 불리언 값으로 반환
  • 프로퍼티 동적 추가 시 에러가 아닌 무시(strict mode에서는 에러)
  • Object.defineProperty 메서드로 추가시 타입 에러

객체 밀봉

  • Object.seal( {객체} ) : 객체 확장 금지 + 프로퍼티 삭제, 프로퍼티 어트리뷰트 재정의 금지
  • 밀봉된 객체는 읽기와 쓰기만 가능
  • Object.isSealed 메서드 : 객체 밀봉 여부 불리언 값으로 반환
  • 프로퍼티 추가와 삭제는 무시되지만(strict mode에선 에러) 프로퍼티 어트리뷰트 재정의 시 type error

객체 동결

  • Object.freeze( {객체} ) : 객체 밀봉 + 프로퍼티 값 갱신 금지
  • 동결된 객체는 읽기만 가능
  • Object.isFrozen 메서드 : 객체 동결 여부 불리언 값으로 반환
const person = {};
Object.defineProperty(person,'firstName', {
  value: "wlrma",
  writable: true,
  enumerable: true,
  configurable: true, 
});

Object.preventExtensions(person);
person.hobby = "singing"; // 무시
Object.defineProperty(person, 'hobby', {
  value: "singing"
})
// TypeError: Cannot define property hobby, object is not extensible

Object.seal(person);
delete person.firstName; // 무시
Object.defineProperty(person, 'firstName', {
  configurable: true,
});
// TypeError: Cannot redefine property: firstName

Object.freeze(person);
console.log(Object.isExtensible(person)); // false
console.log(Object.isSealed(person)); // true
console.log(Object.isFrozen(person)); // true

console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// { value: 'wlrma', writable: false, enumerable: true, configurable: false }
// writable과 configurable이 false가 됨

불변 객체

  • 위의 메서드들은 얕은 변경 방지(shallow only)로 직속 프로퍼티만 변경 방지되고 중첩 객체는 방지 안됨.
  • 객체의 중첩 객체까지 변경 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출
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: 'BDNS',
  address: { city: 'Huam' },
};

deepFreeze(person);

console.log(isFrozen(person.address)); // true

 

Comments