Programming/javascript

자바스크립트 기초부터 모던 자바스크립트까지 - 프로퍼티편

KOOCCI 2022. 7. 19. 23:39

목표 : 객체 프로퍼티에 대해 알아보자.


객체는 자바스크립트의 핵심이다. (함수도 객체다)

이를 토대로, 프로토타입, this 등의 개념으로 확장시켜 나갈 예정이다.

먼저 자바스크립트에서 객체 프로퍼티의 숨은 요소들을 찾아보고, 이후에 함수와 생성자 개념으로 확장시켜 보자.

 

객체의 프로퍼티를 다시한번 꺼내보자.

먼저 프로퍼티가 무엇인가?

기억이 안날 수 있으니 되돌아가보자.

객체 상태를 나타내는 값(Data) 이다.

즉, Key:Value 로 되어 있으며, 객체는 0개 이상의 프로퍼티로 이루어진 집합이다.

 

자바스크립트 엔진은 객체의 프로퍼티를 생성할 때, 기본값으로 자동 정의하는 요소가 있다.

const person = {a:1};

console.log(Object.getOwnPropertyDescriptors(person));
// a: {value: 1, writable: true, enumerable: true, configurable: true}
// [[Prototype]]: Object

먼저 이중 대괄호로 되어있는 [[Prototype]]이 보인다.

모든 객체는 [[Prototype]]이라는 내부 슬롯을 가진다. 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 원칙적으로 접근할 수 없지만, [[Prototype]]은 __proto__를 통해 간접적으로 접근할 수 있다.

더보기

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

내부 메서드 : 위 설명과 동일한 의사 메서드 (psudo method)

 

ECMAScript 사양에 등장하는 이중 대괄호로 감싼 이름들이 내부 슬롯과 내부 메서드다.

자바스크립트 엔진에서 실제로 동작하지만, 개발자가 직접 접근할 수 있도록 공개된 프로퍼티는 아니다.

그리고, Object.getOwnPropertyDescriptors 라는 메서드로 프로퍼티 어트리뷰트를 확인해 보고 있다.

프로퍼티 어트리뷰트 역시, 자바 스크립트 엔진이 관리하는 내부 상태 값이라서 내부 슬롯이지만, 위 메서드로 간접적으로 확인해 본 것이다.

 

즉, 처음으로 돌아가서 자바스크립트 엔진은 객체의 프로퍼티를 생성할 때, 기본값으로 프로퍼티 어트리뷰트를 자동 정의한다.

 

프로퍼티에도 종류가 있을까?

뭔지 모르겠지만, 프로퍼티라는 걸 조금 까보았더니, 프로퍼티 어트리뷰트라는게 나왔다.

 

그럼 또 다른 이야기로 넘어가서, 프로퍼티에는 2가지 종류가 있다.

데이터 프로퍼티 (Data Property) 접근자 프로퍼티 (Accessor Property)
키와 값으로 구성된 일반적인 프로퍼티 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수(Accssor Function)으로 구성된 프로퍼티

데이터 프로퍼티

데이터 프로퍼티는 우리가 일반적으로 확인해본 현재까지의 객체의 프로퍼티로 보면된다.

그리고, 데이터 프로퍼티는 위에서 살펴보았듯이, 다음과 같은 프로퍼티 어트리뷰트를 갖는다.

프로퍼티 어트리뷰트 프로퍼티 디스크립터
객체의 프로퍼티
설명
[[Value]] value 프로퍼티 키를 통해 반환되는 프로퍼티 값
값을 변경할 때, [[Value]]를 재할당 하는 것.
프로퍼티가 없을 때는 동적 생성하고 저장한다.
[[Writable]] writable 프로퍼티 값의 변경 가능 여부를 boolean 값으로 갖는다.
false면, [[Value]]의 값을 변경할 수 없는 읽기 전용으로 만든다.
[[Enumerable]] enumerable 프로퍼티의 열거 가능 여부를 boolean 값으로 갖는다.
for...in 이나 Object.keys 사용 가능 여부가 된다.
[[Configurable]] configurable 프로퍼티의 재정의 가능 여부를 boolean 값으로 갖는다.
false인 경우, 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다.
[[Writable]]이 True라면, [[Value]]의 변경과 [[Writable]]의 변경만 허용된다.

숨겨진 친구들이 설명된다.

즉, 객체라는 것은 0개 이상의 프로퍼티의 집합으로 구성되며, 이 중, 데이터 프로퍼티가 생성될 때에는 위와 같은 프로퍼티 어트리뷰트들이 함께 자동 정의가 되는 것이다.

그리고, 그 설명을 보았을 때, 왜 프로퍼티 어트리뷰트 (속성)이라고 하는 지 명시적으로 알 수 있다.

접근자 프로퍼티

접근자 프로퍼티에서는 또 다른 프로퍼티 어트리뷰트들이 정의된다.

프로퍼티 어트리뷰트 프로퍼티 디스크립터
객체의 프로퍼티
설명
[[Get]] get 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 읽을 때 호출되는 접근자 함수
접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다.
[[Set]] set 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 저장할 때 호출되는 접근자 함수
접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다.
[[Enumerable]] enumerable 프로퍼티의 열거 가능 여부를 boolean 값으로 갖는다.
for...in 이나 Object.keys 사용 가능 여부가 된다.
[[Configurable]] configurable 프로퍼티의 재정의 가능 여부를 boolean 값으로 갖는다.
false인 경우, 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다.
[[Writable]]이 True라면, [[Value]]의 변경과 [[Writable]]의 변경만 허용된다.

접근자 프로퍼티에서는 우리가 흔히 아는 getter, setter 가 자동적으로 정의된다.

다만 getter, setter 모두 정의할 수 있고, 하나만 정의할 수도 있다.

 

예시로 다시한번 보도록 하자.

const person = {
  // 데이터 프로퍼티
  firstName: 'Ungmo',
  lastName: 'Lee',
  // 접근자 프로퍼티 (접근자 함수로 구성)
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  // 접근자 프로퍼티 (접근자 함수로 구성)
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
};

console.log(person.firstName + ' ' + person.lastName);
// Ungmo Lee

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

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

let descriptor = Object.getOwnPropertyDescriptors(person, 'firstName');
console.log(descriptor);
// {firstName: {…}, lastName: {…}, fullName: {…}}
// firstName: {value: 'Heegun', writable: true, enumerable: true, configurable: true}
// fullName: {enumerable: true, configurable: true, get: ƒ, set: ƒ}
// lastName: {value: 'Lee', writable: true, enumerable: true, configurable: true}
// [[Prototype]]: Object

descriptor = Object.getOwnPropertyDescriptors(person, 'fullName');
console.log(descriptor);
// {firstName: {…}, lastName: {…}, fullName: {…}}
// firstName: {value: 'Heegun', writable: true, enumerable: true, configurable: true}
// fullName: {enumerable: true, configurable: true, get: ƒ, set: ƒ}
// lastName: {value: 'Lee', writable: true, enumerable: true, configurable: true}
// [[Prototype]]: Object

위 예시를 통해 프로퍼티에 대해 조금 더 상세히 알아보았다.

번외로, 흔히 헤깔려하는 __proto__ 와 함수의 prototype은 각각 어떤 프로퍼티인지 알아보자

Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// {enumerable: false, configurable: true, get: ƒ, set: ƒ}

Object.getOwnPropertyDescriptor(function(){}, 'prototype');
// {value: {…}, writable: true, enumerable: false, configurable: false}

즉, __proto__접근자 프로퍼티이며, 함수의 prototype 데이터 프로퍼티다.

다음에 생성자 이야기를 하면서 조금 더 구체적으로 알아갈 것이기 때문에, 참고만 하도록 하자.

 

프로퍼티 어트리뷰트도 설정할 수 있다.

객체는 변경이 가능한 값이기 때문에 재할당 없이 직접 변경할 수 있다.

즉, 프로퍼티는 추가, 삭제, 갱신이 가능하며, 프로퍼티 어트리뷰트가 자동 정의가 된다고 하지만, Object.definePropertyObject.defineProperties와 같은 메서드를 통해, 그 값을 직접 넣어줄 수도 있다.

 

또한, 객체의 변경을 방지할 수 있는데, 다음과 같은 메서드들이 있으니 참고하도록 하자.

구분 메서드 프로퍼티
추가
프로퍼티
삭제
프로퍼티
읽기
프로퍼티
쓰기
프로퍼티
어트리뷰트

재정의
객체 확장 금지 Object.preventExtensions X O O O O
객체 밀봉 Object.seal X X O O X
객체 동결 Object.freeze X X O X X

다만, 중첩된 객체에 대해서는 동결이 안된다. 즉, 재귀적으로 동결 시켜주어야 한다.