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

EdLee·2022년 10월 30일

javascript

목록 보기
6/37

16장 프로퍼티 어트리뷰트

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


1.1 내부 슬롯과 내부 메서드

  • js 스펙으로 정의된 내부 프로퍼티로써, 개발자가 직접 접근할 수 있는 외부 프로퍼티는 아니다.
  • prototype과 같이 일부 내부 슬롯이나 내부 메서드에 간접적으로 접근할 수는 있다
const o = {};

o.[Prototype]; //SyntaxError: Unexpected token '['
console.log(o.__proto__); // 콘솔에서 prototype과 프로퍼티, 메서드 확인 가능
/**
Object {}
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
__proto__: null
get __proto__: ƒ get __proto__()
set __proto__: ƒ set __proto__()
*/

1.2 원시 타입의 프로퍼티

  • 책의 16장에서는 객체의 프로퍼티만 다룬다
  • 원시 타입도 프로퍼티가 존재한다
const str = 'Hello';
console.log(str.__proto__);
/**
String ""
anchor: ƒ anchor()
at: ƒ at()
charAt: ƒ charAt()
charCodeAt: ƒ charCodeAt()
codePointAt: ƒ codePointAt()
concat: ƒ concat()
length: 0

... 생략...

toUpperCase: ƒ toUpperCase()
Symbol(Symbol.iterator): undefined
__proto__: Object
*/

책의 설명은 Object 기준이다
원시 타입도 프로퍼티(.length 등), 메서드(.toUpperCase() 등)가 준비되어 있다
참고 : https://ko.javascript.info/primitives-methods

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


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

    프로퍼티의 상태

    프로퍼티의 값(value)
    값의 갱신 가능 여부(wriable)
    열거 가능 여부(enumerable)
    재정의 가능 여부(configurable)

  • 프로퍼티 어트리뷰트는 자바 스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부 슬롯들이다.

    내부 슬롯의 종류 : [[Value]], [[Writable]], [[Enumerable]], [[Confinugrable]]
    Object.getOwnPropertyDescriptor() 메서드로 접근 가능

const person = {
  name: 'Lee'
};

console.log(Object.getOwnPropertyDescriptors(person));
/**
Object {
name: Object
configurable: true
enumerable: true
value: "Lee"
writable: true
}
*/

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

데이터 프로퍼티

  • 키와 값으로 구성된 일반적인 프로퍼티. 위에서 살펴본 모든 프로퍼티는 데이터 프로퍼티
  • 데이터 프로퍼티는 일반 객체의
프로퍼티
어트리뷰트
프로퍼티 디스크립터
객체의 프로퍼티
설명
[[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로 변경하는 것은 허용된다.
const person = {
  name: 'Lee'
};

console.log(Object.getOwnPropertyDescriptors(person, 'name'));
/**
Object {name: {…}}
name: Object
configurable: true
enumerable: true
value: "Lee"
writable: true
*/

접근자 프로퍼티

  • 자체적으로는 값을 갖지 않고, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수(setter, getter)로 구성된 프로퍼티
프로퍼티
어트리뷰트
프로퍼티 디스크립터
객체의 프로퍼티
설명
[[Get]]get- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수
- 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출된다.
[[Set]]set- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수
- 접근자 프로퍼티 키로 프로퍼티 값을 저장하면, 프로퍼티 어트리뷰터 [[Set]]의 값, 즉 setter 함수가 호출된다.
[[Enumerable]]enumerable- 데이터 프로퍼티의 [[Enumerable]]과 같다.
[[Configurable]]configurable- 데이터 프로퍼티의 [[Configurable]]과 같다.

프로퍼티 만들기

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

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

// 데이터 프로퍼티 value의 사용
console.log(person.firstName); //Ed, Value의 호출
person.firstName = 'Jason'; // Value에 할당
console.log(person.firstName); // Jason

// 데이터 프로퍼티 어트리뷰트
// Object {value: "Jason", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));

// 접근자 프로퍼티의 사용
console.log(person.fullName); // Jason Lee, getter가 호출됐다
person.fullName = 'Ed Lee' // setter가 호출됐다
console.log(person.fullName); // Ed Lee

// 접근자 프로퍼티 어트리뷰트
//Object {get: ƒ, set: ƒ, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, 'fullName'));

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

// 함수 객체의 prototype은 데이터 프로퍼티
console.log(Object.getOwnPropertyDescriptor(function() { }, 'prototype'));
// Object {value: {…}, writable: true, enumerable: false, configurable: false}

// 일반 객체의 __proto__는 접근자 프로퍼티
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
// Object {get: ƒ, set: ƒ, enumerable: false, configurable: true}

4. 프로퍼티 정의

  • 프로퍼티 어트리뷰트를 기본값이 아닌 다른 값으로 명시적으로 정의 또는 재정의 하는 것
  • Object.defineProperty() 함수를 사용한다
const person = {
  firstName: '',
  lastName: 'Lee',
};
person.firstName = 'Ed';
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// Object {value: "Ed", writable: true, enumerable: true, configurable: true}

Object.defineProperty(person, 'firstName', {
  writable: false,
});
person.firstName = 'Jason';
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// Object {value: "Ed", writable: false, enumerable: true, configurable: true}

// 다수의 프로퍼티를 한 번에 정의
Object.defineProperties(person, {
  firstName: {
    writable: true,
  },
  lastName: {
    configurable: false,
  }
})
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// Object {value: "Ed", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, 'lastName'));
// Object {value: "Lee", writable: true, enumerable: true, configurable: false}

프로퍼티 어트리뷰트의 기본값

  • 생성자의 기본값과 defineProperty()의 기본값이 다르다
const person = {};
person.firstName = 'Ed'; // 생성자에 의해 프로퍼티가 생성됨
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// Object {value: "Ed", writable: true, enumerable: true, configurable: true}

Object.defineProperty(person, 'lastName', {});
console.log(Object.getOwnPropertyDescriptor(person, 'lastName'));
// Object {value: undefined, writable: false, enumerable: false, configurable: false}
  • 기본값
프로퍼티
어트리뷰트
프로퍼티 디스크립터
객체의 프로퍼티
defineProperty의 기본값생성자의 기본값
[[Value]]valueundefinedValue가 없으면 데이터 프로퍼티 생성 불가
[[Get]]getundefinedundefined, 접근자 프로퍼티로 만드려면 get, set 둘 중 하나는 있어야 함
[[Set]]setundefinedundefined, 접근자 프로퍼티로 만드려면 get, set 둘 중 하나는 있어야 함
[[Writable]]writablefalsetrue
[[Enumerable]]enumerablefalsetrue
[[Configurable]]configurablefalsetrue
const person = {
  set setter(name){}
};

console.log(Object.getOwnPropertyDescriptor(person, 'setter'));
// Object {get: undefined, set: ƒ, enumerable: true, configurable: true}
  • 접근자 프로퍼티와 데이터 프로퍼티 둘 다가 될 수는 없음
const person = {};

Object.defineProperty(person, 'name', {
  value: 'Jason',
  get() {
    return "";
  }
});
// TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

참고 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object

5. 객체 변경 방지

  • 객체는 const라도 재할당 없이 직접 변경할 수 있다.
  • JS는 객체의 변경을 방지하는 다양한 메서드를 제공한다.
  • 이 메서드들은 일부 프로퍼티 어트리뷰트를 활용하지만, 프로퍼티 어트리뷰트
구분메서드프로퍼티
추가
프로퍼티
삭제
프로퍼티
값 읽기
프로퍼티
값 쓰기
프로퍼티
어트리뷰트 재정의
관련 프로퍼티 어트리뷰트
객체 확장 금지Object.preventExtensions()
Object.isExtensible()
XOOOO-
객체 밀봉Object.seal()
Object.isSealed()
XXOOXconfigurable: false
객체 동결Object.freeze()
Object.isFrozen()
XXOXXwritable: false
configurable: false

불변 객체

  • Object.freeze() 메서드는 중첩 객체까지 동결할 수는 없다.
  • 진정한 불변 객체를 구현하려면, 객체 안의 모든 프로퍼티를 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 person1 = {
  name: 'EdLee',
  address: { city: 'Seoul' }
};
Object.freeze(person1);
console.log(Object.isFrozen(person1.address)); // false

const person2 = {
  name: 'EdLee',
  address: { city: 'Seoul' }
};
deepFreeze(person2);
console.log(Object.isFrozen(person2.address)); // true

0개의 댓글