KassyLog

[모던 자바스크립트]프로퍼티 어트리뷰트 본문

javascript

[모던 자바스크립트]프로퍼티 어트리뷰트

Kassy_kim 2023. 1. 8. 01:27

내부슬롯과 내부 메서드

자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에 사용하는 의사 프로퍼티와 의사 메서드다.

자바스크립트 엔진의 내부 로직이므로 원칙적으로 자바스크립트는 내부슬롯과 내부메서드에 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지 않는다. 단, 일부 내부슬롯과 내부메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공하기는 한다.

 

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

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

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

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 }
}
*/

 

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

데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티.

[[Value]] : value - 접근시 반환되는 값
[[Writable]] : writable - 값의 변경 여부를 나타내며 true or false
[[Enumerable]] : enumerable - 열거 기능 여부를 나타내며 true or false // false시 for ... in, Object.keys 메서드 등으로 열거 불가
[[Configurable]]: configurable - 재정의 가능 여부를 나타내며 true or false // false시 삭제, 변경은 불가하지만,[[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용된다

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

[[Get]] : get - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수이며, getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다.
[[Set]] : set - 접근자 프로퍼티를 통해 프로퍼티의 값을 저장할 때 호출되는 함수이며, setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다.
[[Enumerable]] : enumerable - 데이터 프로퍼티와 같다.
[[Configurable]] : configurable - 데이터 프로퍼티와 같다.

 

프로퍼티의 정의

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

const person = {};

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

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

let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');

console.log('firstName', descriptor);
// firstName { value: "Ungmo", writable: true, enumerable: true, configurable: true }

descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log(descriptor);
// lastName { value: "Lee", writable: false, enumerable: false, configurable: false }

console.log(Object.keys(person)); // ["firstName"]

person.lastName = 'Kim';

delete person.lastName;

descriptor = Object.getOwnPropertyDescriptor(person, 'lastName')
console.log('lastName', descriptor);
// lastName { value: "Lee", writable: false, enumerable: false, configurable: false }

Object.defineProperty(person, 'fullName', {
	get() {
    	return this.firstName + ' ' + this.lastName;
    },
    
    set(name) {
    	this.firstName = name.split(' ')[0];
        this.lastName = name.split(' ')[1];
    },
    
    enumerable: true,
    configurable: true
});

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

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

Object.defineProperty 메서드로 프로퍼티를 정의할 때 일부 생략 할 수 있다. 생략했을 때의 기본값은 아래와 같다.

value - undefined
get - undefined
set - undefined
writable - false
enumerable - false
configurable - false

Object.defineProperty는 하나의 프로퍼티만 정의할 수 있다. Object.defineProperties를 사용하면 여러 개의 프로퍼티를 한 번에 정의할 수 있다.

 

객체 변경 방지

객체는 변경 가능한 값이므로 재할당 없이 직접 변경할 수 있다. 즉, 프로퍼티를 추가하거나 삭제할 수 있고, 프로퍼티 값을 갱신할 수 있으며,  Object.defineProperty Ehsms Object.defineProperties 메서드를 사용하여 프로퍼티 어트리뷰트를 재정의할 수도 있다.

 

Object.preventExtensions (객체 확장 금지)

객체의 확장을 금지한다. 확장이 금지된 객체는 프로퍼티 추가가 금지된다.

const person = [ name: 'Lee', isMale: true };

const isExtensible1 = Object.isExtensible(person) // true

Object.preventExtensions(person);

const isExtensible2 = Object.isExtensible(person) // false

person.age = 20 // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }

delete person.name;
console.log(person); // { isMale: true }

Object.defineProperty(person, 'age', { value: 20 });
// TypeError: Cannot define property age, object is not extensible

 

Object.seal(객체 밀봉)

const person = { name: 'Lee', isMale: true };

const isSealed1 = Object.isSealed(person) // false

Object.seal(person);

const isSealed2 = Object.isSealed(person); // true

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

person.age = 20; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }

delete person.name // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }

person.name = 'Kim'; // 값 갱신은 가능하다.
console.log(person); // { name: "Kim", isMale: true }

Object.defineProperty(person, 'name', { configurable: true });
// TypeError: Cannot redefine property: name

 

Object.freeze(객체 동결)

const person = { name: 'Lee' };

const isFrozen1 = Object.isFrozen(person); // false

Object.freeze(person);

const isFrozen2 = Object.isFrozen(person); // true

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

person.age = 20; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }

delete person.name; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }

person.name = 'Kim'; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }

Object.defineProperty(person, 'name', { configurable: true });
// TypeError: Cannot redefine property: name

 

deep freeze(불변객체)

const person = {
	name: 'Lee',
    isMale: true,
    address: {
    	city: 'Seoul'
    }
}

function deepFreeze(target) {
	if (target && typeof target === 'object' && !Object.isFrozen(target)) {
    	Object.freeze(target);
        
        const keys = Object.keys(target); // ['name', 'isMale', 'address']
        const keyLength = key.length; // 3
        for (let i = 0; i < keyLength; i++) {
        	deepFreeze(target[key]));
        }
    }
    
    return target;
}

deepFreeze(person);

const isFrozenPerson = Object.isFrozen(person); // true
const isFrozenAddress = Object.isFrozen(person.address); // true

person.address.city = 'Busan';
console.log(person); // { name: "Lee", isMale: true, address: { city: "Seoul" }}