모던 자바스크립트 Deep Dive 16장 스터디를 통해 정리한 글 😚
퀴즈의 출처는 스터디원들입니다 💛
const zzang = 'hyori'
const amazingPerson = {
zzang: 'hyori'
}
해당 코드를 보면 zzang
변수 값은 재할당이 불가능하지만, amazingPerson
객체에 있는 zzang
프로퍼티 값은 얼마든지 바꿀 수 있다. 따라서 짱은 바뀔 수 있다.
과연 객체 내의 짱이 바뀌지 않게 하려면 어떻게 해야할까?
이를 위해 프로퍼티 어트리뷰트를 이해해야 한다.
이 글은 자바스크립트의 프로퍼티 어트리뷰트에 대해 정리한 글이다.
프로퍼티 어트리뷰트에 대해 얼마나 답할 수 있는지 체크해보자!
키와 값으로 구성된 일반적인 프로퍼티를 무엇이라 부르는지 알고 있다.
자체적으로 값은 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출하는 접근자 함수로 구성된 프로퍼티를 무엇이라 부르는지 알고 있다.
데이터 프로퍼티의 프로퍼티 어트리뷰트로 어떤 것들이 있는지 알고 있다.
접근자 프로퍼티의 프로퍼티 어트리뷰트로 어떤 것들이 있는지 알고 있다.
객체의 [[prototype]]
내부 슬롯에 간접적으로 접근하는 방법을 알고 있다.
프로퍼티 디스크립터 객체를 반환하는 함수를 알고 있다.
기본적으로 프로퍼티를 동적으로 생성했을 때의 프로퍼티 어트리뷰트 값을 알고 있다.
프로퍼티의 어트리뷰트를 직접 정의할 수 있다.
프로퍼티 추가, 삭제, 값 쓰기, 어트리뷰트 재정의를 제어하는 함수들을 알고 있다.
Q1. (난이도 ⭐⭐)
const person = { lastName: 'Hong' }
Object.seal(person)
person.age = 26
console.log(person) // (1)
delete person.lastName
console.log(person) // (2)
person.lastName= 'Park'
console.log(person) // (3)
Object.defineProperty(person, "lastName", {configurable: true}) // (4)
퀴즈 정답
Object.seal 메서드는 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지를 의미한다.(1) { lastName: 'Hong' }
설명 : 프로퍼티 추가 금지
(2) { lastName: 'Hong' }
설명 : 프로퍼티 삭제 금지
(3) { lastName: 'Park' }
설명 : 프로퍼티 값 갱신은 가능
(4) TypeError : Cannot redefine property : lastName
설명 : 프로퍼티 어트리뷰트 재정의 금지
Q2. (난이도 ⭐⭐⭐)
const animal = { name: "nana" }
Object.freeze(animal)
console.log(Object.getOwnPropertyDescriptors(animal))
// {value: "nana", writable: ?, enumerable: ?, configurable: ?}
퀴즈 정답
{value: "nana", writable: false, enumerable: true, configurable: false}
Q2. (난이도 ⭐⭐⭐⭐)
const study = {}
Object.defineProperties(study, {
index: {
value: '16장'
},
title: {
value: '프로퍼티 어트리뷰트',
writable: true,
enumerable: true,
configurable: true
},
chapter: {
get() {
return `${this.index}.${this.title}`;
},
set(string) {
[this.index, this.title] = string.split('.')
},
enumerable: true,
configurable: true
}
})
study.chapter = '17장.생성자 함수에 의한 객체 생성'
console.log(study.chapter)
console.log(Object.keys(study))
퀴즈 정답
16장.생성자 함수에 의한 객체 생성 만약 모든 체크박스에 ✔️ 표시를 하고, 문제 3개를 다 맞혔다면,
이 글에 있는 내용은 다 알고 있다는 뜻이기 때문에 당당하게 뒤로 가기를 눌러도 된다! 😎
🤔 프로퍼티 어트리뷰트란?
➞ 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯, 내부 메서드이다.
🤔 그럼 내부 슬롯과 내부 메서드는 뭔데?
➞ 내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드이다. [[...]]
처럼 이중 대괄호로 감싼 형태로 되어 있다. 하지만 자바스크립트 엔진의 내부 로직이므로 일부 내부 슬롯과 메서드를 제외하면 개발자가 직접 접근하거나 호출할 수는 없다.
예외 중 하나는 프로토타입이다. 모든 객체는 [[Prototype]]
내부 슬롯을 갖는다. 이 내부 슬롯은 __proto__
를 통해 접근할 수 있다.
const o = {}
o.__proto__ // Object.prototype
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분할 수 있다.
✳️ 데이터 프로퍼티
키와 값으로 구성된 일반적인 프로퍼티를 말한다.
프로퍼티 어트리뷰트로는 [[Value]]
[[Writable]]
[[Enumerable]]
[[Configurable]]
를 갖고 있다.
[[Value]]
: 프로퍼티 값을 저장한다.[[Writable]]
: 프로퍼티 값의 변경 여부를 나타내며 불리언 값을 갖는다.[[Enumerable]]
: 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다. false인 경우 for...in
문이나 Object.keys
메서드 등으로 열거할 수 없다.[[Configurable]]
: 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값을 갖는다. 단, [[Writable]]
이 true라면 프로퍼티 값 변경과 [[Writable]]
값을 false로 변경하는 것은 가능하다.const person = {
name: 'Lee'
}
console.log(Object.getOwnPropertyDescriptor(person, 'name'))
// {value: "lee", writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor
메서드를 사용하면 프로퍼티 어트리뷰트를 간접적으로 확인할 수있다.
❗️ 이 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 것이 아니다. 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환하는 것이다. (굉장히 헷갈린다.)
🤔 프로퍼티 디스크립터는 또 뭐야?
➞ [[Value]]
의 디스크립터 프로퍼티는 value
, [[Writable]]
의 디스크립터 프로퍼티는 writable
다. 쉽게 말해 직접적으로 접근하지 않고 이렇게 간접적으로 접근할 수 있는 것이다.
즉 이 메서드를 호출하면 디스크립터 객체를 반환하기 때문에,
{[[Value]]: "lee", [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}
가 아닌
{value: "lee", writable: true, enumerable: true, configurable: true}
값을 확인할 수 있다.
✳️ 접근자 프로퍼티
자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티다.
프로퍼티 어트리뷰트로는 [[Get]]
[[Set]]
[[Enumerable]]
[[Configurable]]
를 갖고 있다.
[[Get]]
: 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수다. 즉, getter
함수가 호출되고 결과가 프로퍼티 값으로 반환된다.[[Set]]
: 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수다. 즉, setter
함수가 호출되고 결과가 프로퍼티 값으로 저장된다.const person = {
firstName: 'Haha',
lastName: 'Lee',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ')
}
}
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다.
person.fullName = 'Wow Lee'
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(person.fullName)
접근자 프로퍼티는 데이터 프로퍼티와 다르게 get
, set
이름으로 메서드를 생성하면 이 메서드들은 getter/setter
함수의 역할을 한다. 여기서 함수의 이름인 fullName
이 바로 접근자 프로퍼티가 된다.
객체에 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존의 프로퍼티의 프로퍼티 어트리뷰트를 재정의할 수 있다.
즉, [[Value]]
[[Writable]]
[[Enumerable]]
[[Configurable]]
값을 변경할 수 있는 것이다. 물론, 직접적으로 접근은 안된다 했으니 o.[[Value]] = 'hi'
처럼 변경하는 것은 당연히 아니다.
✳️ Object.defineProperty
const person = {}
Object.defineProperty(person, 'firstName', {
value: 'Ho',
writable: true,
enumerable: true,
configurable: true
})
Object.defineProperty(person, 'lastName', {
value: 'Lee'
})
이렇게 defineProperty
메서드의 인수로 객체의 참조, 데이터 프로퍼티 키, 프로퍼티 디스크립터 객체를 전달해서 어트리뷰트를 직접적으로 정의할 수 있다.
기본적으로 프로퍼티 디스크립터 객체에서 생략된 어트리뷰트는 기본값이 적용된다.
✳️ 어트리뷰트 기본값
[[Value]]
: undefined[[Get]]
: undefined[[Set]]
: undefined[[Writable]]
: false[[Enumerable]]
: false[[Configurable]]
: false위의 코드에서 'lastName' 키는 value 값만 정의했으므로, writable, enumerable, configurable 값은 모두 false가 기본값이 되었을 것이다.
💡 참고로 프로퍼티 디스크립터 객체는 여러 개를 호출할 수 있고, 정의할 수 있다.
Object.getOwnPropertyDescriptors(프로퍼티 키)
Object.defineProperties(프로퍼티 키, 디스크립터 객체들)
Object.defineProperty
메서드를 사용해 명시적으로 프로퍼티 어트리뷰트 값을 변경해줄 수 있지만, 기본적으로 자바스크립트는 객체의 변경을 방지하는 메서드인 Object.preventExtensions
, Object.seal
, Object.freeze
를 제공해주고 있다.
✳️ Object.preventExtensions
객체의 확장을 금지한다. 즉, 해당 객체에 프로퍼티 추가가 금지되는 것이다. 프로퍼티를 추가하는 방법은 동적 추가와 Object.defineProperty
메서드를 사용하는 방법이 있는데, 이 방법이 모두 금지된다.
확장이 가능한 객체인지 여부는 Object.isExtensible
메서드로 확인할 수 있다.
const person = {name: 'hyori'}
// 확장 금지
Object.preventExtensions(person)
Object.isExtensible(person)
// false 반환
Object.getOwnPropertyDescriptor(person, 'name')
// {value: 'hyori', writable: true, enumerable: true, configurable: true} 반환
이 메서드는 writable, enumerable, configurable이 모두 true로 유지된다.
✳️ Object.seal
객체를 밀봉한다. 밀봉된 객체는 읽기와 쓰기만 가능하다.
밀봉된 객체인지 여부는 Object.isSealed
메서드로 확인할 수 있다.
const person = {name: 'hyori'}
// 프로퍼티 추가, 삭제, 재정의 금지
Object.seal(person)
Object.isSealed(person)
// true 반환
Object.getOwnPropertyDescriptor(person, 'name')
// {value: 'hyori', writable: true, enumerable: true, configurable: false} 반환
이 메서드는 configurable 값만 false가 된다.
✳️ Object.freeze
객체를 동결한다. 동결된 객체는 읽기만 가능하다.
동결된 객체인지 여부는 Object.isFrozen
메서드로 확인할 수 있다.
const person = {name: 'hyori'}
// 프로퍼티 추가, 삭제, 재정의, 쓰기 금지
Object.freeze(person)
Object.isFrozen(person)
// true 반환
Object.getOwnPropertyDescriptor(person, 'name')
// {value: 'hyori', writable: false, enumerable: true, configurable: false} 반환
이 메서드는 writable과 configurable 값이 false가 되는 것을 확인할 수 있다.
💡 참고로 중첩 객체를 동결하려면 재귀적으로 Object.freeze
메서드를 호출해야 한다.
용어도 생소하고, 한 번에 이해하기는 어려우니 요약으로 완벽 이해를 하고 넘어가자.
[[Value]]
[[Writable]]
[[Get]]
과 같은 내부 슬롯, 내부 메서드를 프로퍼티 어트리뷰트라고 부름Object.getOwnPropertyDescriptor
메서드를 이용하는 방법Object.defineProperty
메서드를 이용하는 방법Object.preventExtensions
메서드는 프로퍼티 추가 금지, 하지만 기존 값 변경 가능. 어트리뷰트 재정의 가능Object.seal
메서드는 프로퍼티 삭제와 어트리뷰트 재정의 금지, 하지만 기존 값 변경 가능.Object.freeze
메서드는 프로퍼티 추가, 삭제, 어트리뷰트 재정의, 기존 값 변경 모두 불가능, 하지만 값 읽기는 가능const amazingPerson = {
zzang: 'hyori'
}
Object.freeze(amazingPerson)
이제 짱은 바뀌지 않는다.