[모던 자바스크립트] 5. 객체
모던 자바스크립트 Deep Dive 16~18장을 정리한 글입니다.
프로퍼티
내부 슬롯, 내부 메서드
ECMAscript 사양에 등장하는 이중 대괄호([[…]])로 감싼 이름들에 해당한다. 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAscript 사양에서 사용하는 의사 프로퍼티, 의사 메서드 이다. 대부분 직접 접근이 불가능하지만 일부에 한해 접근할 수 있는 수단을 제공한다.
프로퍼티 어트리뷰트
프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 값이다. 프로퍼티의 상태에는 아래와 같이 있다.
- 값 (value)
- 값의 갱신 가능 여부 (writable)
- 열거 가능 여부 (enumerable)
- 재정의 가능 여부 (프로퍼티 삭제, 프로퍼티 어트리뷰트 값 변경) (configurable)
[[Value]], [[Writable]], [[Enumerable]], [[Configurable]]이 프로퍼티 어트리뷰트에 해당한다. Object.getOwnPropertyDescriptor 메서드를 이용하여 프로퍼티 디스크립터 객체를 통해 프로퍼티 어트리뷰트 정보를 제공받을 수 있다.
프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분한다.
데이터 프로퍼티
키와 값으로 이루어진 일반적인 프로퍼티이며 위의 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 4가지를 프로퍼티 어트리뷰트로 가진다.
접근자 프로퍼티
접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성되어있다. 프로퍼티 어트리뷰트는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]] 으로 구성된다
const person = {
// 데이터 프로퍼티
firstName: 'Hoyoung',
lastName: 'Moon',
// 접근자 프로퍼티
get fullName(){
return `${this.firstName} ${this.lastName}`
}
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
}
console.log(person.fullName) // getter 함수 호출
person.fullName = 'Steve Jobs' // setter 함수 호출
프로퍼티 정의
위와 같이 직접 Object.defineProperty 정적 메서드를 통해 정의가 가능하다.
const person = {};
Object.defineProperty(person, "firstName", {
value: "Hoyoung",
writable: true,
enumerable: true,
configurable: true,
});
또한 객체 변경 방지를 위한 몇가지 메서드가 추가적으로 제공된다.
-
Object.preventExtensions
메서드 이름 그대로 프로퍼티를 확장할 수 없기 때문에 프로퍼티 추가가 금지된다.
-
Object.seal
객체를 밀봉하여 프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트 재정의를 금지한다. 즉, 읽기와 쓰기만 가능하다.
-
Object.freeze
프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트 재정의, 프로퍼티 값 갱신 모두 금지한다. 즉, 읽기만 가능하다.
생성자 함수를 통한 객체 생성
객체 리터럴이 아닌 생성자 함수를 통해 객체를 생성할 경우 같은 객체를 여러개 생성해야할때 효율적으로 생성할 수 있다.
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
const circle1 = new Circle(10);
const circle2 = new Circle(15);
생성자 함수의 인스턴스 생성 과정은 크게 3가지로 나누어진다.
-
인스턴스 생성과 this 바인딩
new를 이용하여 생성자 함수를 호출할 경우 빈 객체(인스턴스)가 암묵적으로 생성되고 이는 this에 바인딩된다.
-
인스턴스 초기화
생성자 함수의 코드가 한 줄씩 실행되어 this에 바인딩된 인스턴스를 초기화한다. 위의 Circle의 경우 인수로 받은 radius를 이용하여 프로퍼티 (radius), 메서드 (getDiameter)를 초기화한다.
-
인스턴스 반환 모든 처리가 끝난 후 this가 암묵적으로 반환된다.
함수도 객체이지만 일반 객체와 달리 [[Call]], [[Construct]] 같은 내부 메서드를 추가로 가지고 있다. 일반 함수로 호출시 [[Call]]이, 생성자 함수로 호출시 [[Construct]]가 호출된다.
생성자 함수로 호출될 수 있는 함수로 constructor라 하고 호출될 수 없는 경우 non-constructor이다. 함수 정의 방식에 따라 아래와 같이 나뉘어진다.
- constructor: 함수 선언문, 함수 표현식, 클래스
- non-constructor: 메서드(ES6 메서드 축약 표현), 화살표 함수
const circle = Circle(5);
console.log(circle); // undefined
console.log(radius); // 5
console.log(getDiameter()); // 10
만약 위와 같이 new 연산자를 붙이지 않고 생성자 함수를 호출할 경우 [[Construct]]가 아닌 [[Call]]이 호출되고 일반 함수이므로 this에 전역 객체 window가 바인딩된다. 따라서 radius, getDiameter는 전역객체의 프로퍼티와 메서드가 된다.
이러한 연산자 없이 호출하는 일을 방지하기 위해 new.target을 이용할 수 있다. new.target은 함수 내부의 지역 변수와 같이 사용되는 메타 프로퍼티이다. new 연산자와 함께 호출될 시 함수 자신을 가리키고, 일반 함수로 호출시 undefined를 나타낸다.
function Circle(radius) {
if (!new.target) return new Circle(radius);
this.radius = radius;
this.getDiameter = function () {
return 2 * radius;
};
}
const circle = Circle(5);
console.log(circle.getDiameter); // 10
함수와 일급 객체
일급 객체는 다음과 같은 조건들을 만족해야 한다.
- 무명의 객체 리터럴로 생성할 수 있어야 한다. (런타임 생성 가능)
- 변수나 자료구조에 저장할 수 있다.
- 함수의 매개변수 또는 반환값으로 사용할 수 있다.
이는 함수에 적용해도 모두 만족한다. 즉, 객체와 동일하게 사용할 수 있는 함수는 일급 객체이다.
함수는 객체이므로 프로퍼티를 가질 수 있으며 함수만 가지고 있는 특별한 프로퍼티들이 있다.
arguments
arguments는 함수 호출 시 전달된 인수들이 정보를 담고 있는 순회 가능한 유사 배열 객체이다. 유사 배열 객체이므로 인수의 개수를 length 프로퍼티로 가지며 인수를 프로퍼티 값으로 가지고 키는 인수의 순서를 나타낸다.
length
함수를 정의할 때 선언한 매개변수의 개수를 가리킨다.
name
함수 객체의 이름을 나타낸다. 익명 함수 표현식의 경우 ES6부터 함수 객체를 가리키는 식별자를 값으로 갖는다.