- Published on
모던자바스크립트 16.프로퍼티 어트리뷰트
- Authors
- Name
- Bora Choi
프로퍼티 어트리뷰트를 들어가기전에 ...
내부 슬롯(internal slot)과 내부 메서드(internal method)
자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티(pseudo property)
와 의사 메서드(pseudo method)
.
자바스크립트 엔진의 내부 로직이므로 원칙적으로 직접 접근하거나 호출할 수 없지만 일부에 한하여 간접적으로 접근할 수 있는 수단을 제공하기는 한다.
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태
를 나타내는 프로퍼티 어트리뷰트
를 기본값으로 자동 정의한다.
-
프로퍼티 상태란
프로퍼티의 값(value), 값의 갱신 가능 여부 (writable), 열거 가능 여부 (enumerable) , 재정의 가능 여부(configurable)
프로퍼티 어트리뷰트 : 자바스크립트 엔진이 관리하는 내부 상태값. [[Value]],[[Writable]],[[Enumerable]],[[Configurable]]
Object.getOwnPropertyDescriptor
메서드를 사용하여 간접적으로 확인할 수 있다.(하나의 프로퍼티에 대해 프로퍼티 디스크립터 객체 반환)
const person = {
name: 'Choi',
}
//프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환
console.log(Object.getOwnPropertyDescriptor(person, 'name')) // Object.getOwnPropertyDescriptor(객체참조 , 프로퍼티 키)
//{value:"Choi",writable:true,enumerable:true,configurable:true} => 프로퍼티 디스크립터 객체 반환
//존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined 반환.
-
Object.getOwnPropertyDescriptors
ES8부터 도입. 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환
const person = { name: 'Lee', } person.age = 20 console.log(Object.getOwnPropertyDescriptors(person)) /*{ name : {value:"Lee",writable:true,enumerable:true,configurable:true}, age :{value:20,writable:true,enumerable:true,configurable:true} }*/
데이터 프로퍼티와 접근자 프로퍼티
데이터 프로퍼티(data property)
키와 값으로 구성된 일반적인 프로퍼티.
데이터 프로퍼티의 프로퍼티 어트리뷰트
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 값 | 값의 타입 | 설명 |
---|---|---|---|---|
[[Value]] | value | 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값 | 프로퍼티 키의 값의 타입 | 키를 통해 프로퍼티 값을 변경하면 [[Value]] 에 값을 재할당한다. 이때 프로퍼티가 없으면 프로퍼티를 동적으로 생성하고 생성된 프로퍼티의 [[Value]] 에 값을 저장한다. |
[[Writable]] | writable | 프로퍼티 값의 변경 가능 여부 | boolean | - false 인 경우 프로퍼티의 [[Value]] 의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다. |
[[Enumerable]] | enumerable | 프로퍼티의 열거 가능 여부 | boolean | - false인 경우 해당 프로퍼티는 for...in 문이나 Object.keys 메서드 등으로 열거할 수 없다 |
[[Configurable]] | configurable | 프로퍼티의 재정의 가능 여부 | boolean | - false 인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트의 값 변경이 금지된다. |
- 단,
[[Writable]]
이 true인 경우[[Value]]
의 변경과[[Writable]]
을 false로 변경하는 것은 허용한다.
[[Value]]
는 프로퍼티 값
으로 초기화 되고, [[Writable]]
,[[Enumerable]]
,[[Configurable]]
는 true
로 초기화 된다.
접근자 프로퍼티(accessor property)
자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 떄 호출되는 접근자함수로 구성된 프로퍼티.
접근자 프로퍼티의 프로퍼티 어트리뷰트
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
---|---|---|
[[Get]] | get | - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다. |
[[Set]] | set | - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수다. setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다. |
[[Enumerable]] | enumerable | - 데이터 프로퍼티 [[Enumerable]] 과 같다. |
[[Configurable]] | configurable | - 데이터 프로퍼티 [[Configurable]] 과 같다. |
const person = {
//데이터 프로퍼티
firstName: 'Aspyn',
lastName: 'Choi',
//fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
//getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`
},
//setter 함수
set fullName(name) {
//배열 디스트럭처링 할당
;[this.firstName, this.lastName] = name.split(' ')
},
}
person.age = 29
//데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(person.firstName + ' ' + person.lastName) // Aspyn Choi
//접근자 프로퍼티를 통한 프로퍼티 값의 저장
//접근자 프로퍼티 fullName에 값ㅇ르 저장하면 setter함수가 호출된다.
person.fullName = 'Amy Cho'
console.log(person) // {firstName : "Amy", lastName : "Cho"};
//접근자 프로퍼티를 통한 프로퍼티의 값의 참조
//접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(person.fullName) //"Amy Cho"
//firstName은 데이터 프로퍼티다
//[[Value]] , [[Writable]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName')
console.log(descriptor) //{value: "Amy", writable: true, enumerable: true, configurable: true}
//fullName은 접근자 프로퍼티다.
//[[Get]],[[Set]],[[Enumerable]],[[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName')
console.log(descriptor) //{enumerable: true, configurable: true, get: ƒ, set: ƒ}
프로퍼티 정의
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰터를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의 하는것.
Object.defineProperty
메서드를 사용해 프로퍼티의 어트리뷰트를 정의할 수 있다.
-
Object.defineProperty(obj, prop, descriptor)
obj
속성을 정의할 객체.
prop
새로 정의하거나 수정하려는 속성의 이름 또는
Symbol
.descriptor
새로 정의하거나 수정하려는 속성을 기술하는 객체.
var o = {} // 새로운 객체 생성
// 데이터 속성 서술자와 defineProperty로
// 새로운 속성을 추가하는 예시
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true,
})
o = {
a: 37,
}
// 'a' 속성이 o 객체에 존재하고 값은 37
// 접근자 속성 기술자와 defineProperty로
// 새로운 속성을 추가하는 예시
var bValue = 38
Object.defineProperty(o, 'b', {
// ES2015 단축 메서드명 사용
// 아래 코드와 같음
// get: function() { return bValue; }
// set: function(newValue) { bValue = newValue; },
get() {
return bValue
},
set(newValue) {
bValue = newValue
},
enumerable: true,
configurable: true,
})
o.b // 38
// 'b' 속성이 o 객체에 존재하고 값은 38
// o.b를 재정의하지 않는 이상
// o.b의 값은 항상 bValue와 동일함
// 두 가지를 혼용할 수 없음
Object.defineProperty(o, 'conflict', {
value: 0x9f91102,
get: function () {
return 0xdeadbeef
},
})
// TypeError 발생
// value는 데이터 서술자에만,
// get은 접근자 서술자에만 나타날 수 있음
객체 변경 방지
객체 변경을 금지하는 메서드.
객체 방지 메서드
구분 | 메서드 | 추가 | 삭제 | 읽기 | 쓰기 | 재정의 |
---|---|---|---|---|---|---|
객체 확장 금지 | Object.preventExstensions | x | o | o | o | o |
객체 밀봉 | Object.seal | x | x | o | o | x |
객체 동결 | Obeject.freeze | x | x | o | x | x |
객체 확장 금지 Object.preventExtensions
프로퍼티 추가가 금지
// Object.preventExtensions는 확장 불가된 객체를 반환합니다.
var obj = {}
var obj2 = Object.preventExtensions(obj)
obj === obj2 // true
// 객체는 기본으로 확장 가능입니다.
var empty = {}
Object.isExtensible(empty) // === true
// ...하지만 바뀔 수 있습니다.
Object.preventExtensions(empty)
Object.isExtensible(empty) // === false
// Object.defineProperty는 확장 불가 객체에 새 속성을 추가할 때 오류가 발생합니다.
var nonExtensible = { removable: true }
Object.preventExtensions(nonExtensible)
Object.defineProperty(nonExtensible, 'new', { value: 8675309 }) // TypeError 발생
// 엄격 모드에서, 확장 불가 객체에 새 속성을 추가하려는 시도는 TypeError가 발생합니다.
function fail() {
'use strict'
nonExtensible.newProperty = 'FAIL' // TypeError 발생
}
fail()
// 확장 (__proto__(는 사라집니다. 대신 Object.getPrototypeOf를 쓰세요)를
// 지원하는 엔진에서만 동작합니다):
// 확장 불가 객체의 프로토타입은 불변합니다.
var fixed = Object.preventExtensions({})
fixed.__proto__ = { oh: 'hai' } // TypeError 발생
객제 밀봉 Object.seal
읽기와 쓰기만 가능
var obj = {
prop: function () {},
foo: 'bar',
}
// 새 속성이 추가되고, 기존 속성은 변경되거나 제거될 수 있음
obj.foo = 'baz'
obj.lumpy = 'woof'
delete obj.prop
var o = Object.seal(obj)
// 밀봉한 객체의 속성값은 밀봉 전과 마찬가지로 변경할 수 있음
obj.foo = 'quux'
obj.foo // 'quux' 가 출력됨
// 데이터 속성과 접근자 속성 사이의 전환은 불가
Object.defineProperty(obj, 'foo', {
get: function () {
return 'g'
},
}) // TypeError 발생
// 속성값의 변경을 제외한 어떤 변경도 적용되지 않음
obj.quaxxor = 'the friendly duck' // 에러가 나지는 않지만 속성은 추가되지 않음
delete obj.foo // 에러가 나지는 않지만 속성이 삭제되지 않음
// strict mode 에서는 속성값의 변경을 제외한 모든 변경은 TypeError 발생
function fail() {
'use strict'
delete obj.foo // TypeError 발생
obj.sparky = 'arf' // TypeEror 발생
}
fail()
// Object.defineProperty() 메서드를 이용한 속성의 추가도 TypeError 발생
Object.defineProperty(obj, 'ohai', { value: 17 }) // TypeErorr 발생
Object.defineProperty(obj, 'foo', { value: 'eit' }) // 속성값의 변경은 가능함
객체 동결 Object.freeze
읽기만 가능
var obj = {
prop: function () {},
foo: 'bar',
}
// 동결 이전: 새 속성을 추가할 수 있고,
// 기존 속성을 변경하거나 제거할 수 있음
obj.foo = 'baz'
obj.lumpy = 'woof'
delete obj.prop
// 동결
var o = Object.freeze(obj)
// 반환 값은 전달된 객체와 동일함.
o === obj // true
// 객체가 동결 상태가 됨.
Object.isFrozen(obj) // === true
// 이제 모든 변경 시도는 실패함
obj.foo = 'quux' // 조용하게 아무것도 하지 않음
// 조용하게 속성을 추가하지 않음
obj.quaxxor = 'the friendly duck'
// 엄격 모드에서는 이러한 시도에 대해 TypeError 발생
function fail() {
'use strict'
obj.foo = 'sparky' // TypeError 발생
delete obj.foo // TypeError 발생
delete obj.quaxxor // 'quaxxor' 속성은 추가된 적이 없으므로 true 반환
obj.sparky = 'arf' // TypeError 발생
}
fail()
// Object.defineProperty를 통한 변경 시도
// 아래 두 구문 모두에서 TypeError 발생
Object.defineProperty(obj, 'ohai', { value: 17 })
Object.defineProperty(obj, 'foo', { value: 'eit' })
// 프로토타입을 변경하는 것 또한 불가함
// 아래 두 구문 모두에서 TypeError 발생
Object.setPrototype(obj, { x: 20 })
obj.__proto__ = { x: 20 }
불변 객체
객체 방지 메서드들은 얕은 변경 방지(shallow only)
로 직속 프로퍼티만 변경이 방지
된다.
중첩 객체까지 동결
하여 변경이 불가능한 읽기전용의 불변 객체를 구현하려면 모든 프로퍼티에 재귀적으로 Object.freeze 메서드를 호출
해야한다.
문제
-
프로퍼티 상태 4가지
프로퍼티의 값(value), 값의 갱신 가능 여부 (writable), 열거 가능 여부 (enumerable) , 재정의 가능 여부(configurable)
-
데이터 프로퍼티의 프로퍼티 어트리뷰트는 무엇인지, 초기화는 어떻게 되는가
[[Value]]
,[[Writable]]
,[[Enumerable]]
,[[Configurable]]
[[Value]]
는 프로퍼티 값
으로 초기화 되고, [[Writable]]
,[[Enumerable]]
,[[Configurable]]
는 true
로 초기화 된다.
-
프로퍼티 쓰기 가능한 객체 변경방지 메소드는?
객체 확장 금지(Object.preventExtenstions),객체 밀봉(Object.seal)
본 포스팅은 모던자바스크립트 deep dive를 공부하면서 정리한 내용입니다.