딥다이브 : 프로퍼티 어트리뷰트

jonyChoiGenius·2023년 10월 2일
0

16.1 내부 슬롯과 내부 메서드

내부 슬롯, 내부 메서드 : 자바스크립트 엔진의 구현 원리를 설명하기 위해 사용하는 의사 프로퍼티와 의사 메서드

개발자가 직접 접근할 수 있는 프로퍼티는 아니다.

(단, [[Prototype]]__proto__ 를 통해 간접적으로 접근할 수 있다.)

const o = {}
o.[[Prototype]] // SyntaxError
o.__proto__ // Object.prototype

ECMAScript는 JavaScript 표준 구현체의 명세를 정의한다.
JavaScript 엔진은 이러한 ECMAScript 준하여 JavaScript를 읽고 실행하는 프로그램 혹은 인터프리터이다. 구현하는 방식에는 JavaScript 엔진마다 차이가 있다. 가령 크롬 V8 엔진은 C++로 작성되었다.
내부 슬롯은 객체의 내부 상태를 관리하기 위한 비공개 프로퍼티이며, JavaScript 엔진에서 객체의 동작 및 상태를 구현하는 데에 사용된다.

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

프로퍼티 어트리뷰트

프로퍼티의 상태를 나타내는 속성.

프로퍼티가 만들어질 때 자바스크립트 엔진에 의해 기본적으로 만들어지는 내부 슬롯이다.

프로퍼티의 상태

프로퍼티는 네 가지의 상태를 갖는다.

값value, 갱신 가능 여부writable, 열거 가능 여부enumerable, 재정의 가능 여부configurable

프로퍼티 디스크립터

프로퍼티의 상태를 객체 형태로 기술한 것을 디스크립터 객체라 부른다.

Object.getOwnPropertyDescriptor(obj, prop)

프로퍼티 어트리뷰트에 간접적으로 접근하여 프로퍼티의 상태를 객체 형태descriptor 반환한다.

const person = {
	name: 'Lee'
}

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

Object.getOwnPropertyDescriptors(obj)

ES8에서 추가되었다. 모든 프로퍼티의 디스크립터를 반환한다.

const person = {
	name: 'Lee'
}
person.age = 20;

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

16.3 데이터 프로퍼티, 접근자 프로퍼티

데이터 프로퍼티

키와 값으로 구성된 일반적인 프로퍼티

접근자 프로퍼티

자체적인 값 없이 다른 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수(accessor funtion)로 구성된 프로퍼티

데이터 프로퍼티

데이터 프로퍼티를 생성하면 다음의 프로퍼티 어트리뷰를 갖는다.

[[Value]]
[[Writable]]Writable이 false이면 읽기 전용 프로퍼티가 된다.
[[Enumerable]]Enumerable이 false으면 for…in이나 Obejct.keys 등으로 열거할 수 없다.
[[Configurable]]Configurable이 false이면 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다. 단 [[Writable]]이 true이면 [[Value]]와 [[Writable]]의 변경은 허용된다.

접근자 프로퍼티

접근자 프로퍼티는 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티다.

| [[Get]] | 접근자 프로퍼티로 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. 즉 getter 함수.
접근자 프로퍼티로 접근할 시 [[Get]]의 값, 즉 getter 함수의 결과가 값으로 반환된다. |
| --- | --- |
| [[Set]] | 접근자 프로퍼티로 데이터 프로퍼티에 값을 저장할 때 호출되는 접근자 함수. setter 함수가 호출되고, 그 결과가 값으로 저장된다. |
| [[Enumerable]] | |
| [[Configurable]] | |

const person = {
	firstName: 'Ungmo',
	lastName: 'Lee',

	get fullName(){
		return `${this.firstName} ${this.lastName}`;
	},

	set fullName(name) {
		[this.firstName, this.lastName] = name.split(' ');
	}
};

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

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

console.log(Object.getOwnPropertyDescriptor(person, 'fullName'));
//{get: f, set: f, enumerable: true, configurable: true}

* getter와 setter란?

https://inpa.tistory.com/entry/JS-📚-getter-setter-란

getter와 setter는 내부 프로퍼티에 직접 접근하지 않고, 메서드를 통해 조회/변경하는 것을 의미한다.

메서드를 통해 프로퍼티에 접근함으로써

  1. 정보를 은닉한다.
  2. 코드의 안정성과 유지보수성을 높일 수 있다.

ES6부터 추가된 get과 set 키워드를 활용하면 메서드를 getter와 setter 함수로 정의할 수 있다.


// ES6 이전 방식
const user = {
		name: 'inpa',
    
    // 객체의 메서드(함수)
    getName() {
    	return user.name;
    },
    setName(value) {
			// 이름이 문자열이 아닌 경우 에러를 빠른 반환
			if(typeof value !== 'string') return console.error("이름은 문자열만 입력 가능합니다.")
			user.name = value;
    }
}

console.log(user.getName()); // inpa
user.setName('tistory');
// ES6 이후
const user = {
    name: 'inpa',

    get username() {
        return user.name;
    },

    set username(value) {
        if(typeof value !== 'string') return console.error("이름은 문자열만 입력 가능합니다.")
        user.name = value;
    }
}

console.log(user.username); // "inpa"
user.username = 400 // "이름은 문자열만 입력 가능합니다."

get과 set으로 선언한 메서드는 외부에서볼 때 ‘프로퍼티’처럼 보인다.

프로퍼티처럼 접근하면 결과값을 반환하고,

프로퍼티처럼 할당하면 setter 함수를 거쳐 프로퍼티의 Value를 수정한다.

__proto__는 대표적인 접근자 프로퍼티로, 데이터 프로퍼티인 Object.prototype에 간접적으로 접근할 수 있도록 한다.

const des1 = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__')
console.log(des1)
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}

const des2 = Object.getOwnPropertyDescriptor(function(){}, 'prototype')
console.log(des2)
// {value: {…}, writable: true, enumerable: false, configurable: false}

프로퍼티 정의

프로퍼티 정의 : 새로운 프로퍼티를 추가하며 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티 어트리뷰트를 재정의하는 것.

Object.defineProperty(obj, prop, descriptor) : prop에 새로운 객체를 정의하고, descirptor를 객체 형태로 넘겨준다.

const person = {};

Object.defineProperty(person, 'firstName', {
		value: 'Ungmo',
		writable: true,
		enumerable: true,
		configurable: true
});

Object.defineProperty(person, 'lastName', {
		value: 'Lee'
});

// 접근자 프로퍼티는 아래와 같이 get과 set에 메서드 형태로 정의할 수 있다.
/* 
Object.defineProperty(person, 'fullname', {
	get: function() { 
					return this.firstName + ' ' + this.lastName;
	},
  set: function(name) { 
					[this.firstName, this.lastName] = name.split(' ');
  },
	enumerable: true,
	configurable: true,
}
*/

// 위의 메서드 정의를 ES6의 단축 메서드 명으로 정의하면 아래와 같다.
Object.defineProperty(person, 'fullname', {
	get() { 
					return this.firstName + ' ' + this.lastName;
	},
  set(name) { 
					[this.firstName, this.lastName] = name.split(' ');
  },
	enumerable: true,
	configurable: true,
}

console.log(person.fullName) // Ungmo Lee

Descirptor객체에서 프로퍼티를 정의하지 않은 경우 기본값과 함께 정의된다.

프로퍼티 디스크립터 객체의 프로퍼티기본값
valueundefined
getundefined
setundefined
writablefalse
enumerablefalse
configurablefalse

Object.defineProperties를 통해 여러 개의 프로퍼티를 한 번에 정의할 수 있다.

const person = {};

Object.defineProperties(person, {
  firstName: {
    value: "Ungmo",
    writable: true,
    enumerable: true,
    configurable: true,
  },
  lastName: {
    value: "Lee",
    writable: true,
    enumerable: true,
    configurable: true,
  },
  // 접근자 프로퍼티 정의
  fullName: {
    get() {
      return `${this.firstName} ${this.lastName}`;
    },
    set(name) {
      [this.firstName, this.lastName] = name.split(" ");
    },
    enumerable: true,
    configurable: true,
  },
});

객체 변경 방지

객체는 변경 가능한mutable 값이므로 재할당 없이 변경이 가능하다.

이를 방지하기 위해 객체 확장 금지, 객체 밀봉, 객체 동결의 역할을 하는 메서드를 사용할 수 있다.

역할메서드프로퍼티 추가프로퍼티 삭제프로퍼티 값 읽기프로퍼티 값 쓰기프로퍼티 어트리뷰트 재정의
객체 확장 금지Object.preventExtensionsXOOOO
객체 밀봉Object.sealXXOOX
객체 동결Object.freezeXXOXX

객체 밀봉 상태에서 writable은 true이고, configurable은 false이다.

객체 동결 상태에서 writable은 false이고, configurable 역시 false이다.

객체 변경 방지 여부는 다음의 메서드로 확인할 수 있다.

구분메서드
객체 확장 금지Object.isExtensible
객체 밀봉Object.isSealed
객체 동결Object.isFrozen

객체 확장 금지 : 프로퍼티 추가가 금지된다.

객체 밀봉 : 프로퍼티 값의 읽기와 갱신만 가능하다.

객체 동결 : 프로퍼티 값의 읽기만 가능하다.

불변 객체

객체 변경 방지 메서드는 얕은 방지shallow only로 작동한다.

중첩된 객체를 변경방지 하기 위해서는 재귀적으로 동결을 진행해야한다.

function deepFreeze(target) {
  // target이 객체이고, 동결상태가 아닐 때
  if (target && typeof target === "object" && !Object.isFrozen(target)) {
    Object.freeze(target);
    // 오브젝트 내부의 프로퍼티들에 대해 재귀적으로 호출
    Object.keys(target).forEach((key) => deepFreeze(target[key]));
  }
}

const person = {
  name: "Lee",
  address: { city: "Seoul" },
};

deepFreeze(person);

person.address.city = "Pusan"; // 중첩된 객체의 프로퍼티의 값의 갱신을 시도한다.
console.log(person.address.city); // 'Seoul' - 객체가 동결되어 갱신되지 않았다.
profile
천재가 되어버린 박제를 아시오?

0개의 댓글