My Boundary As Much As I Experienced

내부 슬롯과 내부 메서드, 프로퍼티 어트리뷰트 본문

FrontEnd/Javascript(Vanilla)

내부 슬롯과 내부 메서드, 프로퍼티 어트리뷰트

Bumang 2024. 2. 22. 13:11

정의

내부 슬롯과 내부 메서드는 ECMAScript에서 정의한 의사 프로퍼티Pseudo Property, 의사 메서드Pseudo Method이다.

(css의 가상 선택자Pseudo Selector처럼, 실제로 있는 태그는 아니지만 기능 상 존재하는..? 느낌인듯!)

ECMAScript 사양에 정의된 대로 구현되고 JS 엔진에서 실제로 동작하는 방식을 구성한다.

그러나 개발자가 직접 접근하거나 호출할 수 있도록 공개된 객체의 프로퍼티는 아니다.

 

내부 슬롯과 내부 메서드는 형태는 이중 대괄호로 감싸서 표현된다.

 

 

실제 확인 방법

우리가 한 번 쯤 들어본 [[prototype]]도 내부 슬롯이다. 직접 [[prototype]]을 프로퍼티 체이닝으로 접근할수는 없지만

객체를 console.log해보면 개발자 도구에서 맨 하단에서 확인할 수 있다.

const o = {};

// 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 직접 접근할 수 없다.
o.[[prototype]] // -> Uncaught SyntaxError: Unexpected token '['
// 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공하기는 한다.
o.__proto__ // -> Object.prototype

 

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

 

__proto__는 [[Prototype]]이란 내부 슬롯에 접근하기 위한 프로퍼티이지만 es6부터는 공식적인 표준은 아니게 되었다.

대신에 Object.getPrototypeOf()Object.setPrototypeOf() 메서드를 사용하여 객체의 프로토타입에 접근하는 것이 권장된다.

 

 

 

직접적으로 접근할 수 없는 이유

캡슐화(encapsulation): 내부 슬롯은 객체의 내부 상태를 나타내며, 이를 외부에서 직접적으로 조작하는 것은 객체의 캡슐화 원칙을 위반할 수 있다. 객체의 내부 상태에 대한 접근을 허용하면 다른 코드에서 이를 부적절하게 변경할 수 있으며, 이는 코드의 안정성과 예측 가능성을 저해할 수 있기 때문이다.

 

호환성과 보안(Security): 직접적인 내부 슬롯 접근은 보안 문제를 야기할 수 있다. 언어의 특정 구현에 의존하는 코드는 플랫폼이나 환경에 따라 다르게 동작할 수 있으며, 이는 코드의 이식성과 호환성을 저해할 수 있다.

 

 

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

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

프로퍼티의 상태란 프로퍼티의 값[[Value]], 값의 갱신 가능여부[[Writable]], 열거 가능 여부[[Enumerable]], 재정의 가능 여부[[Configurable]] 등이 있다.

 

이런 프로퍼티 어트리뷰트에 직접 접근할 수는 없지만, Object.getOwnPropertyDescriptor() 메서드를 사용하여 간접적으로 확인할 수는 있다.

이 때 첫 번째 매개변수에 객체의 참조, 두 번째 매개변수에는 프로퍼티 키를 문자열로 전달하면 프로퍼티 디스크립터 객체(property attributes)를 반환합니다. 만약 존재하지 않는 프로퍼티를 제공하면 undefined가 뜬다.

Object.getOwnPropertyDescriptor(객체참조, 프로퍼티키)

const person = {
  name: 'Lee'
};

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

 

 

데이터 프로퍼티, 접근자 프로퍼티란?

데이터 프로퍼티
키와 값으로 구성된 일반적인 프로퍼티, 모든 프로퍼티는 데이터 프로퍼티이다. (앞서 예시로 든 것들은 모두 데이터 프로퍼티다)

  1. [[Value]] : 프로퍼티 키를 통해 프로퍼티 값을 접근하면 반환되는 값이다. 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당한다. 이 때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티 [[Value]]에 값을 저장한다.
  2. [[Writable]] : 프로퍼티 값의 변경 가능 여부를 나타내며 boolean 값을 갖는다.
  3. [[Enumerable]] : 프로퍼티의 열거 가능 여부를 나타내며 boolean 값을 갖는다. false이면 for ... in 이나 Object.keys 메서드 등으로 열거할 수 없다.
  4. [[Configurable]] : 프로퍼티의 재정의 가능 여부를 나타내며 boolean값을 갖는다. false이면 삭제나 변경이 금지 된다. 단, [[Writable]]이 true인 경우는 value 변경과 writable을 false로 변경하는 것이 허용된다.

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

  1. [[Get]] : 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 읽을 때 호출되는 접근자 함수이다. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환되는 것이다.
  2. [[Set]] : 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수이다. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다.
  3. [[Enumerable]] : 데이터 프로퍼티의 enumerable과 같다.
  4. [[Configurable]] : 데이터 프로퍼티의 configurable과 같다.

접근자 함수는 getter/setter라고 불리고 정의가능하다.

 

프로퍼티 정의 (프로퍼티 어트리뷰트 정의)

프로퍼티 정의란 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것을 말한다. 예를 들어 프로퍼티 값을 갱신 가능하도록 할 것인지, 프로퍼티를 열거 가능하도록 할 것인지, 프로퍼티를 재정의 가능하도록 할 것인지 정의할 수 있다. 이를 통해 객체의 프로퍼티가 어떻게 동작해야 하는지를 명확히 정의할 수 있다.

 

Object.defineProperty() 메서드를 사용하면 프로퍼티의 어트리뷰트를 정의할 수 있다. 인수로는 객체의 참조와 데이터 프로퍼티의 키인 문자열, 프로퍼티 디스크립터 객체를 전달한다.

 

  • Object.defineProperty 메서드 : 한번에 하나의 프로퍼티만 정의할 수 있다.
  • Object.defineProperties 메서드 : 여러 개의 프로퍼티를 한 번에 정의할 수 있다.

[[Writable]]을 수정 못하게 막는 예시

const person = {};

Object.defineProperty(person, 'name',{
  writable: false, // 값 수정 불가 처리
  configurable: true,
  enumerable: true,
  value: 'name'
}); 

person.name = 'sungIn'; // 수정을 못하게 해서 변경되지 않습니다.
console.log(person.name); // name

 

객체 변경 감지 (확장금지, 밀봉, 동결)

 

Object.preventExtension(obj) => 추가가 안 된다.

Object.seal(obj) => 추가, 삭제, 재정의가 안 된다.

Object.freeze(obj) => 읽기 빼곤 다 안 된다.