(선언 단계 + 초기화 단계)일단 const 키워드를 사용하자. 재할당이 필요하면 그때 let으로 바꾸자.
내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드이다. ECMAScript 사양에 등장하는 이중 대괄호([[…]])로 감싼 이름들이 내부 슬롯과 내부 메서드다.
const o = {};
o.[[Prototype]] //nope.안돼안돼 __proto__와 같이 간접적으로 접근할 수 있다.
o.__proto__ //-> Object.prototype
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다. 프로퍼티의 상태란 프로퍼티의 값(value), 값의 갱신 가능 여부(writable), 열거 가능 여부(enumerable), 재정의 가능 여부(configurable)을 말한다.
프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯 아래와 같다.
내부 슬롯: [[Value]], [[Writable]], [[Enumerable]], [[Configurable]]Object.getOwnPropertyDescriptor 메서드로 확인const person = {
name: 'Lee'
};
//person의 name 프로퍼티 속사정이 궁금할때 🤩
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {value: 'Lee', writable: true, enumerable: true, configurable: true}
person.age = 20; //프로퍼티 동적 생성!
console.log(Object.getOwnPropertyDescriptors(person)); //s만 붙였을 뿐인데!
/*
{
name: {value: 'Lee', writable: true, enumerable: true, configurable: true},
age: {value: 20, writable: true, enumerable: true, configurable: true}
}
*/
프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분할 수 있다.
데이터 프로퍼티는 다음과 같은 프로퍼티 어트리뷰트를 갖는다. 프로퍼티 생성할 때 자동 정의된다.
| 프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
|---|---|---|
| [[Value]] | value | - 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값이다. |
| - 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당한다. 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값을 저장한다. | ||
| [[Writable]] | writable | - 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 갖는다. |
| - [[Writable]]의 값이 false인 경우 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다. | ||
| [[Enumerable]] | enumerable | - 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다. |
| - [[Enumerable]]의 값이 false인 경우 해당 프로퍼티는 for…in 문이나 Object.keys 메서드 등으로 열거할 수 없다. | ||
| [[Configurable]] | configurable | - 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값을 갖는다. |
| - [[Configurable]]의 값이 false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다. 단, [[Writable]]이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용된다. |
접근자 프로퍼티는 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티다.
| 프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
|---|---|---|
| [[Get]] | get | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수다. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다. |
| [[Set]] | set | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수다. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다. |
| [[Enumerable]] | enumerable | 데이터 프로퍼티의 것과 같다. |
| [[Configurable]] | configurable | 데이터 프로퍼티의 것과 같다. |
접근자 함수는 getter/setter 함수라고도 부른다. 접근자 프로퍼티는 getter와 setter 함수를 모두 정의할 수도 있고 하나만 정의할 수도 있다.
const person = {
firstName: 'DongHyun',
lastName: 'Kim',
//getter 함수
get fullName(){
return `${this.firstName} ${this.lastName}`;
}
//setter 함수
set fullName(name){
[this.firstName, this.lastName]= name.split(' ');
}
};
person.fullName = 'Heegun Lee';
console.log(person.fullName);
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
//{value: "Heegun", writable: true, enumerable: true, configurable: true}
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
//{get: 𝑓, set: 𝑓, enumerable: true, configurable: true}
접근자 프로퍼티 fullName으로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메서드가 호출되어 다음과 같이 동작한다.
Object.getOwnPropertyDescriptor 메서드로 접근자 프로퍼티와 데이터 프로퍼티를 구별한다. (찍어 보면 내부 슬롯이 다르니까..ㅎㅎ)
Object.defineProperty 메서드로 프로퍼티를 정의할 수 있다.
| 프로퍼티 디스크립터 객체의 프로퍼티 | 대응하는 프로퍼티 어트리뷰트 | 생략했을 때의 기본값 |
|---|---|---|
| value | [[Value]] | undefined |
| get | [[Get]] | undefined |
| set | [[Set]] | undefined |
| writable | [[Writable]] | false |
| enumerable | [[Enumerable]] | false |
| configurable | [[Configurable]] | false |
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(descriptor);
//{value: "Ungmo", writable: true, enumerable: true, configurable: true}
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log(descriptor);
//{value: "Lee", writable: false, enumerable: false, configurable: false}
console.log(Object.keys(person)); //["firstName"]
//enumerable: false인 lastName은 안보인다.ㅎ
person.lastName = 'Kim'; //writable: false라서 무시된다.
delete person.lastName; //configurable: false라서 무시된다.
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log(descriptor);
//{value: "Lee", writable: false, enumerable: false, configurable: false}
//변한게 없죠? ㅎㅎ
Object.defineProperty(person, 'fullName', {
get(){
return `${this.firstName} ${this.lastName}`;
},
set(name){
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
});
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
//{get: 𝑓, set: 𝑓, enumerable: true, configurable: true}
Object.defineProperties 를 사용하면 여러개를 한번에 정의할 수 있다.
const person = {};
Object.defineProperties(person, {
firstName: {
value: 'DongHyun',
writable: true,
enumerable: true,
configurable: true
},
fullName: {
get() {
return this.firstName;
},
set(name) {
this.firstName = name;
},
enumerable: true,
configurable: true
}
});
객체는 변경 가능한 값이므로 재할당 없이 직접 변경할 수 있다. 이런 변경을 방지하기 위해 아래 메서드들이 있다.
| 구분 | 메서드 | 확인 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
|---|---|---|---|---|---|---|---|
| 객체 확장 금지 | Object.preventExtensions | Object.isExtensible | X | O | O | O | O |
| 객체 밀봉 | Object.seal | Object.isSealed | X | X | O | O | X |
| 객체 동결 | Object.freeze | Object.isFrozen | X | X | O | X | X |
프로퍼티 추가 금지
읽기와 쓰기만 가능
읽기만 가능
객체의 중첩 객체까지 동결
new Object(); 이외에도 String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수가 있다.
직관적이고 좋지만 단 하나의 객체만 생성한다. 동일한 프로퍼티를 갖는 객체를 여러 개 생성해야 하는 경우 매번 같은 프로퍼티를 기술해야 하기 때문에 비효율 적이다.
객체는 프로퍼티를 통해 고유의 상태를 표현한다. 그리고 메서드를 통해 상태 데이터인 프로퍼티를 참조하고 조작하는 동작을 표현한다.
생성자 함수에 의한 객체 생성 방식은 마치 객체(인스턴스)를 생성하기 위한 템플릿(클래스)처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
function Circle(radius){
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
};
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
🥪 **this**
this는 객체 자신의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수다. this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
| 함수 호출 방식 | this가 가리키는 값(this 바인딩) |
|---|---|
| 일반 함수로서 호출 | 전역 객체 |
| 메서드로서 호출 | 메서드를 호출한 객체 |
| 생성자 함수로서 호출 | 생성자 함수가 생성할 인스턴스 |
생성자 함수의 역할은 인스턴스를 생성하는 것과 생성된 인스턴스를 초기화(인스턴스 프로퍼티 추가 및 초기값 할당)하는 것이다.
인스턴스 생성과 this 바인딩
암묵적으로 빈 객체가 생성되고 this에 바인딩된다.
인스턴스 초기화
생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다.
인스턴스 반환
this가 반환된다. 다른 객체를 명시적으로 반환하면 this가 반환되지 못한다. 원시 값을 반환하면 무시된다.
일반 객체는 호출할 수 없지만 함수는 호출할 수 있다. 따라서 함수 객체는 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드는 물론, 함수로서 동작하기 위해 함수 객체만을 위한 [[Environment]], [[FormalParameters]] 등의 내부 슬롯과 [[Call]], [[Construct]] 같은 내부 메서드를 추가로 지니고 있다.
일반 함수로서 호출되면 [[Call]]이 호출되고 생성자 함수로서 호출되면 [[Construct]]가 호출된다.
함수 객체는 callable이면서 constructor이거나 callable이면서 non-constructor다. 즉, 모든 함수 객체는 호출할 수 있지만 모든 함수 객체를 생성자 함수로서 호출할 수 있는 것은 아니다.
new로 함수 호출하면 constructor가 호출된다.
new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다. new 연산자 없이 일반 함수로서 호출된 함수 내부의 new.target은 undefined다.