JS06-1 생성자 함수

생성자 함수로 객체 생성하기 / 객체 리터럴과 생성자 함수 비교 / 생성자 함수의 인스턴스 생성 과정 / 내부 메서드 / new 연산자

2022-02-04에 씀

본 시리즈는 모던 자바스크립트 Deep Dive 책을 참고하여 작성하고 있습니다.

생성자 함수로 객체 생성하기

객체는 다양한 방식으로 생성할 수 있다. 앞에서는 객체 리터럴로 객체를 생성하는 방식을 알아보았다.

1const obj = {
2 name: "kim",
3 age: 32
4}

이번에는 생성자 함수를 통해서 객체를 생성하는 방식에 대해 알아본다.

1const obj = new Object();
2const obj2 = new Circle(5);

생성자 함수(constructor)는 new 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수이다. 생성자 함수에 의해 생성된 객체인스턴스(instance)라 한다. 자바스크립트에서 제공하는 빌트인 생성자 함수를 사용할 수도 있고, 직접 생성자 함수를 만들어 쓸 수도 있다. 위 예시의 Object 외에도 String, Number, Boolean, Function, Array, Date, RegExp, Promise 와 같은 빌트인 생성자 함수가 제공된다. Object 빌트인 생성자 함수는 빈 객체를 생성해서 반환한다.

객체 리터럴과 생성자 함수 비교

객체 리터럴에 의한 객체 생성

1const circle = {
2 radius: 5,
3 getDiameter() {
4 return 2 * this.radius;
5 }
6};
7
8circle.getDiameter();

생성자 함수에 의한 객체 생성

1function Circle(radius) {
2 this.radius = radius; // this는 미래 생성될 인스턴스를 가리킴
3 this.getDiameter = function () {
4 return 2 * this.radius;
5 }
6}
7
8const circle = new Circle(5);
9circle.getDiameter();

생성자 함수의 인스턴스 생성 과정

생성자 함수의 역할은 1) 인스턴스를 생성하고 2) 생성된 인스턴스를 초기화하는 것이다. 초기화 과정에서 인스턴스의 프로퍼티를 추가하고 초기값을 할당하는 일을 한다. 생성자 함수는 인스턴스를 반드시 생성해야 하고, 초기화는 할 수도 있고 하지 않을 수도 있다.

그런데 위의 "생성자 함수에 의한 객체 생성" 예시 코드를 보면 생성자 함수 내에서는 인스턴스 초기화를 위한 동작만 하고 있다. 그런데도 인스턴스를 반환받을 수 있는 이유는, new 연산자와 함께 생성자 함수를 호출하면 자바스크립트 엔진이 암묵적 처리를 통해 인스턴스를 생성하고 반환하기 때문이다.

  1. 인스턴스 생성과 this 바인딩

이 과정은 함수 실행 런타임 이전에 실행된다.

  1. 암묵적으로 빈 객체를 생성 → 추후 반환될 인스턴스 객체
  2. 인스턴스를 this에 바인딩 → 생성자 함수 내부 this가 인스턴스를 가리키게 됨

바인딩: 식별자와 값을 연결하는 과정. this가 가리킬 객체를 바인딩했다는 뜻이고, 생성자 함수 내부에서의 this는 인스턴스를 가리키게 된다.

  1. 인스턴스 초기화

생성자 함수 내부 코드가 한 줄씩 실행된다.

  1. 인스턴스에 프로퍼티나 메서드 추가
  2. 초기값을 할당하여 초기화하거나 고정값 할당
  3. 인스턴스 반환
  4. 바인딩된 this가 암묵적으로 반환
  5. 명시적으로 반환된 객체가 있다면 return 문에 명시한 객체를 반환
  6. 명시적으로 반환되는 원시 값이 있다면 원시 값을 무시하고 this 반환
1function Circle(radius) {
2 this.radius = radius;
3 return radius;
4}
5
6const circle = new Circle(3);
7console.log(circle); // { radius: 3 }

생성자 함수의 기본 동작은 바인딩된 this를 반환하는 것이다. 따라서 다른 값을 반환하는 것은 생성자 함수의 동작 방식을 훼손하는 것이므로 예측을 어렵게 만든다. 따라서 생성자 함수 내부에서는 return 문을 사용하지 않아야 한다.

내부 메서드

함수 선언문이나 함수 표현식으로 정의한 함수는 일반 함수로 호출할 수도 있고 생성자 함수로서 호출할 수도 있다. 그리고 함수는 객체이므로 일반 객체와 동일하게 동작할 수 있다. 함수는 일반 객체가 가지는 내부 슬롯과 내부 메서드를 가지고, 추가로 함수로서 동작하기 위한 내부 슬롯과 내부 메서드를 가진다.

즉, 함수 객체는 callable이면서, constructor이거나 non-constructor이다. 모든 함수 객체는 호출 가능하지만, 생성자 함수로서 호출할 수 있을 수도 있고 없을 수도 있다. 호출할 수 없는 객체는 함수 객체가 아니므로 모든 함수 객체는 반드시 callable이다.

함수가 constructor인지 판단하는 기준은 함수를 정의한 방식이다.

1// 일반 함수로 정의된 경우
2function foo() {}
3const bar = function () {};
4const obj = {
5 x: function() {}
6};
7
8const ins_1 = new foo();
9const ins_2 = new bar();
10const ins_3 = new obj.x();
11
12// non-constructor
13const arrow = () => {};
14const obj_2 = {
15 x() {}
16};
17
18const ins_arr = new arrow(); // TypeError
19const ins_met = new obj_2.x(); // TypeError

new 연산자

new 연산자와 함께 함수를 호출하면 그 함수는 생성자 함수로 동작한다. 이 경우 함수 객체 내부 메서드 [[Construct]]가 호출된다. 이 때 함수는 constructor여야 한다. new 연산자 없이 생성자 함수를 호출하면 일반 함수로 호출된다. 즉, 내부 메서드 [[Call]]이 호출된다.

생성자 함수와 일반 함수 간에 형식적인 차이는 없기 때문에, 일반적으로 생성자 함수의 경우 함수 이름의 첫 문자를 대문자로 기술하여 일반 함수와 구분한다.

new.target

생성자 함수를 일반 함수로 호출하면, 함수 내에서 사용한 this는 전역 객체를 가리킨다. 따라서 new 키워드를 제외할 경우 전역 객체에 의도치 않게 프로퍼티나 메서드를 추가하게 될 수도 있다.

1function Circle(radius) {
2 this.radius = radius;
3 this.getDiameter = function () {
4 return this.radius * 2;
5 }
6}
7
8Circle(2);
9
10console.log(window.radius); // 2
11console.log(window.getDiameter()); // 4

이러한 위험을 방지하기 위해 new.target 을 지원한다. 이를 메타 프로퍼티라고 부른다. new.target은 ES6 문법으로, IE에서는 new.target이 지원되지 않는다. 이는 constructor인 함수 내부에서 암묵적인 지역 변수처럼 사용된다.

1function Circle(radius) {
2 // 일반 함수로 호출된 경우
3 if (!new.target) {
4 return new Circle(radius);
5 }
6
7 this.radius = radius;
8 this.getDiameter = function () {
9 return this.radius * 2;
10 }
11}

new.target을 사용 불가능한 경우에는 스코프 세이프 생성자 패턴을 사용할 수 있다.

1function Circle(radius) {
2 // 생성자 함수로 생성되었다면 -> this가 Circle에 바인딩 됨
3 // 일반 함수로 생성되었다면 -> this는 전역 객체를 가리킴
4 if (!(this instanceof Circle)) {
5 return new Circle(radius);
6 }
7
8 this.radius = radius;
9 this.getDiameter = function () {
10 return this.radius * 2;
11 }
12}

인스턴스는 프로토타입에 의해 생성자 함수와 연결된다. 대부분의 빌트인 생성자 함수도 new 연산자와 함께 호출되었는지 확인한 후 값을 반환한다.

프로필 사진

조예진

이전 포스트
JS05 - 전역 변수의 문제점 & 프로퍼티 어트리뷰트
다음 포스트
JS06-2 프로토타입