자바스크립트 프로퍼티 어트리뷰트

csb·2021년 1월 16일
0

자바스크립트

목록 보기
4/7

내부 슬롯과 내부 메서드

내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 사용한다.
자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아니다.
원칙적으로 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지 않는다.
단, 일부 간적접으로 접근할 수 있는 수단을 제공하기는 한다.
내부 슬롯의 경우, _ _proto__를 통해 간접적으로 접근할 수 있다.

const o = {};
o.[[Prototype]] // Uncaught SyntaxError: Unexpected token '['
o.__proto__ // Object.prototype

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

프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯이다.
프로퍼티 어트리뷰트에 직접 접근할 수 없지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인할 수는 있다.

const person = {
	name: 'Lee'
};

console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// { value: "Lee", writable: true, enumerable: true, configurable: true}
// value 이외의 값은 아래에 설명 예정

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

const person2 = {
	name: "Lee2"
}

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

console.log(Object.getOwnPropertyDescriptor(person123, name'))
// Uncaught ReferenceError: person123 is not defined

console.log(Object.getOwnPropertyDescriptor(person2, 'name2'))
// undefined

ES8에서 도입된 Object.getOwnPropertDescriptors 메서드는 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환한다.

const person = {
	name: "Lee",
    isMale: true,
}

person.age = 20;

console.log(Object.getOwnPropertyDescriptors(person));

/*
{
	name: { value: "Lee", writable: true, enumerable: true, configurable: true },
    isMale: { value: true, wirtable: 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로 변경하는 것은 허용된다
const person = {
	name: 'Lee'
};

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


const person2 = {
	name: "Lee2",
    isMale: true,
}

person2.age = 21;

console.log(Object.getOwnPropertyDescriptors(person2));

/*
{
	name: { value: "Lee2", writable: true, enumerable: true, configurable: true },
    isMale: { value: true, wirtable: true, enumerable: true, configurable: true },
    age: { value: 21, writable: true, enumerable: true, configurable: true }
}
*/

접근자 프로퍼티

  • [[Get]] : get - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수이며, getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다.
  • [[Set]] : set - 접근자 프로퍼티를 통해 프로퍼티의 값을 저장할 때 호출되는 함수이며, setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다.
  • [[Enumerable]] : enumerable - 데이터 프로퍼티와 같다
  • [[Configurable]] : configurable - 데이터 프로퍼티와 같다
const person = {
	firstName: 'Ungmo',
    lastName: 'Lee',
    
    get fullName() {
    	return this.firstName + ' ' + this.lastName;
    },
    
    set fullName(name) {
    	this.firstName = name.split(' ')[0];
        this.lastName = name.split(' ')[1];
    }
};

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.getOwnPropertyDescriptor(person, 'firstName');

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

descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');

console.log(descriptor);
// {get: f, set: f, enumerable: true, configurable: true }

프로퍼티 정의

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.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'
    }
};

Object.freeze(person);

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

person.address.city = 'Busan';
console.log(person); // { name: "Lee", isMale: true, address: { city: "Busan" }}
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" }}

참고

0개의 댓글