본문 바로가기
JavaScript

Property Attribute

by oncerun 2022. 3. 3.
반응형

프로퍼티 어트리뷰트를 이해하기 위해선 내부 슬롯 ( internal slot)과 내부 메서드( internal method)의 개념을 알아야 한다.

 

 

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

의사 프로퍼티 (pseudo property)와 의사 메서드( pseudo method)이다. ECMAScript 사양에 등장하는 이중 대괄호

[[...]]로 감싼 이름들이 내부 슬롯과 내부 메서드이다. 

 

그럼 [[prototype]]같은 것들도 내부 슬롯이고 이들은 자바스크립트 엔진의 구현 알고리즘을 설명하기 위한 의사 프로퍼티였다는 것을 알게 된다. 

 

ECMAScript 사양에 정의된 내부 슬롯과 내부 메서드는 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 프로퍼티는 아니다. 즉 자바스크립트 엔진의 내부 로직이므로 접근할 수 있는 방법을 제공하지는 않는다. 다만 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공하기도 한다.

 

[[Prototype]]이라는 내부 슬롯은 우리가 쉽게 접할 수 있는 프로퍼티이다. 우리는 [[Prototype]]이라는 내부 슬롯에 직접 접근할 수는 없지만 __proto__를 통해 간접적으로 접근할 수 있다.

const a ={}

a.[[Prototype]] // SyntaxError

a.__proto__ // Object.prototype

 

 

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

 

프로퍼티의 상태란 프로퍼티의 값(value), 값의 갱신 가능 여부(writable), 열거 가능 여부(enumerable), 재정의 가능 여부(configurable)를 말한다.  

이들은 내부 슬롯으로 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]]이다.  객체의 내부 슬롯에 직접 접근할 수 없지만 이들을 나타내는 프로퍼티 어트리뷰트가 있기 때문에 다음 메서드를 통해 간접적으로 확인할 수 있다.

const person = {
	name : 'OnceRun'
};

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

 

이때 반환하는 객체가 바로 프로퍼티 디스크립터(Property Descriptor) 객체이다. 만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined가 반환된다.

 

이 메서드는 하나의 프로퍼티에 대해서만 프로퍼티 디스크립터 객체를 반환한다. 그래서 ES8에서 도입된 

Object.getOwnPropertyDescriptors 메서드는 모든 프로퍼티의 프로퍼티 어트리뷰트를 제공하는 프로퍼티 디스크립터 객체들을 반환한다.

 

 

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

 

프로퍼티는 두 가지로 구분될 수 있다.

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

접근자 프로퍼티를 처음 들었을 때 Java의 Setter함수를 생각할 수 있다.  setter함수 역할을 하는 프로퍼티가 존재하고 이를 사용하면 프로퍼티의 값을 변경하거나 가져올 수 있다는 것 같다. 자바스크립트의 함수는 1급 시민이니까..

 

 

1) 데이터 프로퍼티

 

데이터 프로퍼티는 다음과 같은 어트리뷰트를 갖는다. 이러한 프로퍼티 어트리뷰트는 자바스크립트 엔진이 프로퍼티를 생성할 때 기본값으로 자동 정의한다.

 

1.1) [[Value]]

 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값이다. 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]] 내부 슬롯에 값을 재할당하고 만약 프로퍼티가 존재하지 않으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값을 저장한다.

 

!! 생성자 함수에서 this는 생성될 인스턴스를 가리키고 이는 암묵적인 빈객체를 만들어 프로퍼티가 바인딩된 this를 반환한다. 이 빈객체는 초기에는 프로퍼티가 존재하지 않을 것이다. 

하지만 [[Value]] 내부 슬롯의 설명을 보면 존재하지 않으면 프로퍼티를 동적 생성한다고 한다. 따라서 빈객체에 프로퍼티를 추가할 수 있는 것이다. 

 

자바에서는 Class에 정의되지 않는 필드를 동적 생성하고 값을 할당하는 것은 불가능하지만 자바스크립트는 가능한 것 같다. 

 

1.2) [[Writable]]

 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 갖는다. [[Writable]]의 값이 false인 경우 해당 프로퍼티 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다. (클로저?))

 

1.3) [[Enumerable]]

 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다. 만약 false라면 해당 프로퍼티는 for...in 문이나 Object.keys 메서드 등으로 열거할 수 없다. 

 

1.4) [[Configurable]]

 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값을 갖는다. false라면 해당 프로퍼티의 삭제, 값의 변경이 금지된다.

다만 [[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용된다.

 

 

기본 설정은 프로퍼티가 생성될 때 [[Value]]의 값은 프로퍼티 값으로 초기화되며 [[Writable]], [[Enumerable]], [[Configurable]]의 값은 true로 초기화된다. 동적 추가도 마찬가지이다.

 

 

지금까지 정리하면 개발자는 자바스크립트의 내부 슬롯, 내부 메서드에 직접 접근할 수 없다. 다만 프로퍼티 어트리뷰트 객체를 통해 내부 슬롯을 확인하거나 값을 변경할 수 있다.

 

 

 

2) 접근자 프로퍼티

 

접근자 프로퍼티 accessor property는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티이다. 이들도 프로퍼티이기 때문에 프로퍼티 어트리뷰트를 갖는다..

 

2.1) [[Get]]

 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수이다. 즉 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 [[Get]]에 할당된 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다.

 

2.2) [[Set]]

 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출하는 접근자 함수로 접근자 프로퍼티 키로 값을 저장하면 setter함수의 호출하고 그 결과가 프로퍼티 값으로 저장된다. (여기서 값이란 [[Value]] 내부 슬롯일 것이다.)

 

2.3) [[Enumerable]]

 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다. 만약 false라면 해당 프로퍼티는 for...in 문이나 Object.keys 메서드 등으로 열거할 수 없다. 

 

2.4) [[Configurable]]

 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값을 갖는다. false라면 해당 프로퍼티의 삭제, 값의 변경이 금지된다.

다만 [[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용된다.

 

const person = {

    firstName :  'Once',
    lastName :   'Run',
    
    get fullName(){
    	return `${this.firstName} ${this.lastName}`;
    }
    ,
    set fullName(name){
    	
        [this.firstName, this.lastName] = name.split(' ');
    }
   
};

//setter
person.fullName = 'change name';

//getter
person.fullName;

 

Person 객체의 first 프로퍼티는 데이터 프로퍼티이고 fullName은 접근자 프로퍼티이다.

 

따라서 각각의 디스크립터 객체에 담긴 프로퍼티 어트리뷰트가 다른 것을 알 수 있다.

메서드 앞에 get, set이 붙은 메서드가 바로 getter와 setter 함수이고 함수의 이름인 fullName이 접근자 프로퍼티이다. 

 

이를 내부 슬롯/메서드 관점에서 보면 접근자 프로퍼티로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메서드가 호출되어 다음과 같이 동작한다.

 

  • 프로퍼티 키의 유효성을 검사한다. 키는 문자열 또는 심벌이어야 한다. 
  • 프로토타입 체인에서 프로퍼티를 검색한다. 
  • 검색된 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다.
  • 접근자 프로퍼티인 경우 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수를 호출하여 그 결과를 반환한다. 이 값은 Object.getOwnPropertyDescriptor 메서드가 반환하는 객체의 get 프로퍼티 값과 동일하다.

 

* 프로토타입을 까먹었다면?

 - 프로토타입은 어떤 객체의 상위 객체의 역할을 하는 객체이다. 프로토타입은 하위 객체에게 자신의 프로퍼티와 메서드를 상속한다. 프로토타입 객체의 프로퍼티나 메서드를 상속받은 하위 객체는 자신의 프로퍼티 또는 메서드인 것처럼 자유롭게 사용할 수 있다.

 

 

프로퍼티를 정의해보자.

 

정의란 새로운 프로퍼티를 추가하면서 이 프로퍼티의 값이 갱신 가능하도록 할 것인지, 열거 가능한지, 재정의 가능한지, 값이 무엇인지 정의하는 것이다.

 

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

 

 

 만약 디스크립터 객체의 프로퍼티를 누락시키면 false가 기본값이다.

 

enumerable을 통해 참조할 수 있는 [[Enumerable]]의 값이 false라면 for..in문이나 Object.keys 등으로 열거할 수 없다.

 

person 객체의 name 속성이 보이지 않는다. 이는 [[Enumerable]]의 값이 false이기 때문이다.

 

만약 writable 값이 false인데 값을 변경하려고 하면 에러는 발생하지 않고 무시된다. (이게 더 무섭다...)

name이 변경되지 않았다.

 

[[Configurable]]값이 false라면 프로퍼티를 재정의할 수 없다.  정의의 개념은 위에 나와있다. 또한 프로퍼티의 삭제 또한 불가능하다.

 

 

기본 값으로 [[Writable]], [[Enumerable]],  [[Configurable]]은 false지만 [[Value]], [[Get]], [[Set]]은 undefined이 기본값이다.

 

 

객체라는 것은 상태를 가지고 이 상태들이 객체의 행동에 따라 변하면서 자신의 역할을 수행한다.  객체는 프로퍼티를 추가, 삭제, 갱신, 정의할 수 있다. 

자바스크립트는 이러한 변경을 막을 수 있는 다양한 메서드들을 제공한다.

 

  • Object.preventExtensions
  • Object.seal
  • Object.freeze

다만 이러한 변경 방지 메서드 들은 얕은 변경 방지(shallow only)로 중첩 객체까지는 영향을 주지 못한다. 

깊은 복사, 얕은 복사할 때 그 얕음을 뜻한다. 

 

이 경우에는 재귀적으로 Object.freeze 메서드를 통해 처리해야 한다.

 

 

반응형

'JavaScript' 카테고리의 다른 글

ES6 함수  (0) 2022.03.05
클래스 (2)  (0) 2022.03.03
클래스 (1)  (0) 2022.03.02
-집가서 확인  (0) 2022.03.02
클로저의 활용  (0) 2022.02.24

댓글