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

soonrok·2021년 10월 25일
0

자바스크립트 독학

목록 보기
16/48
post-thumbnail

해당 포스팅은 위키북스의 "모던 자바스크립트 Deep Dive"라는 책을 독학하며 기록하는 글입니다.


객체 리터럴에 의한 객체 생성 방식은 가장 일반적이고 간단한 객체생성 방식이다. 하지만 이 외에도 객체를 생성할 수 있는 다양한 방법들이 있는데 이번 포스팅에서는 '생성자 함수'에 의한 객체 생성 방법을 알아보자.

// 객체 리터럴에 의한 객체 생성
const Object1 = {
  color: "red",
  shape: "circle",
  border: "none",
}

Object 생성자 함수

new 연산자와 함께 Object생성자 함수를 호출하면 빈 객체를 생성하여 반환한다. 이렇게 빈 객체를 생성한 이후 프로퍼티 또는 메서드를 추가해서 객체를 완성할 수 있다.

// Object생성자 함수를 통한 객체 생성
const Object2 = new Object();
Object2.color = "blue";
Object2.shape = "triangle";
Object2.border = "none";

console.log(Object2);  // {color: "blue", shape: "triangle", border: "none"}

생성자 함수란 new 연산자와 함께 호출하여 객체를 생성하는 함수를 말한다. 이때 생성자 함수에 의해 생성된 객체를 인스턴스라 한다.


생성자 함수를 사용하는 이유

그럼 간편하게 객체 리터럴를 통해 객체를 생성하면 되는데 왜 생성자 함수를 사용하는 걸까? 이유는 다음과 같다.

  1. 객체 리터럴을 통한 객체 생성한 단 하나의 객체만 생성한다.

  2. 프로퍼티의 값은 객체마다 다를 수 있지만, 메서드의 내용은 대부분 동일하다.

위의 객체 리터럴을 통해 Object1을 생성하는 코드를 보면 코드 5줄을 사용해서 1개의 객체를 생성했다. 만약 전체 코드에서 객체를 하나만 생성하는 코드라면 상관없겠지만 100개 1,000개를 생성해야 된다면 어떨까? 5,000줄의 코드를 쓰는게 과연 맞을까...?

또한 객체의 메서드는 대부분 객체의 프로퍼티 값들을 참조해서 반환하거나 계산하는 식의 메서드이다. 이 내용은 각기 다른 객체가 만들어졌다고 해도 동일한 내용을 가지게 될 것이고 그럼 코드의 반복을 피할 수 없을 것이다.


커스텀한 생성자 함수 생성과 사용

생성자 함수는 이름 그대로 객체를 생성하는 함수이다. 앞서 말한 것처럼 생성자 함수는 new 연산자와 같이 쓰이며 만약 new 연산자와 함께 호출하지 않으면 생성자 함수가 아닌 일반함수로 동작한다. (당연히 원하는 결과가 나오지 않을 것이다)

생성자 함수의 역할은 프로터피 구조가 동일한 인스턴스를 생성하기 위한 템플릿으로서 동작하여 인스턴스를 생성하는 것과 생성된 인스턴스를 초기화하는 것이다. (초기화는 개발자의 자유) 하지만 생성자 함수의 내부를 살펴보면 생성된 인스턴스를 return하는 부분은 없다. 이는 자바스크립트 엔진이 암묵적인 처리를 통해 인스턴스를 생성하고 반환하기 때문이다. 다음의 생성자 함수 동작 과정을 보자.

  1. 인스턴스 생성과 this바인딩
    -> 이 과정에서 생성자 함수 내에서 new Object()를 하지 않아도 암묵적으로 새 인스턴스가 생성된다. 그리고 새로 생성된 인스턴스는 해당 생성자 함수 내에서 쓰이는 this에 바인딩된다.

  2. 인스턴스 초기화
    -> 개발자가 생성자 함수내에 기술한 내용을 바탕으로 빈 인스턴스의 값들이 초기화된다. 이때 this.프로퍼티명 = 프로퍼티값꼴의 문을 사용하면 된다.

  3. 인스턴스 반환
    -> 개발자의 의도대로 초기화된 인스턴스가 자바스크립트 엔진에 의해 암묵적으로 반환된다. 이때 return 키워드를 통해 명시적으로 다른 객체를 반환하면 명시한 다른 객체가 반환되고, 원시 값을 반환하면 해당 원시 값을 무시하고 this에 바인딩된 생성자 함수가 새로 만든 인스턴스 객체가 반환된다. 하지만 생성자 함수 내에서 this가 아닌 다른 값을 반환하는 것은 생성자 함수의 기본 동작을 훼손하기 때문에 생성자 함수에서는 return을 반드시 생략하는 것이 좋다.

생성자 함수는 일반적으로 첫 문자를 대문자로 기술하는 파스칼 케이스로 명명하여 일반 함수와 구별할 수 있도록 노력한다.

// 커스텀하게 만든 생성자 함수와 이를 통한 객체 생성
function Rectangle(width, height, color) = {
  this.width = width;
  this.height = height;
  this.color = color;
  this.getArea = function() {
    return this.width * this.height;
  };
}

const Object3 = new Rectangle(3, 4, "red");
const Object4 = new Rectangle(5, 2, "black");
const Object5 = new Rectangle(7, 9, "blue");

내부 메서드 [[Call]]과 [[Construct]]

함수의 내부에 내부 메서드 [[Call]]을 갖는 함수 객체를 callable이라고 하며 callable은 호출할 수 있는 객체를 말한다. 마찬가지로 [[Construct]]를 갖는 함수 객체를 constructor, 갖지 않는 객체를 non-constructor라고 하며 constructor는 생성자로서 호출할 수 있는 함수, non-constructor는 생성자로서 호출할 수 없는 함수를 의미한다.

우리가 알고 있는 모든 함수는 호출할 수 있으므로 [[Call]]을 가지고 있다. 하지만 [[Construct]]는 가질 수도, 안 가질 수도 있는데 이를 어떻게 구분할까? 간단하게 함수 선언문과 함수 표현식으로 정의된 함수는 [[Construct]]를 가지고 있고, ES6에 추가된 화살표 함수와 메서드 축약 표현으로 정의된 함수는 [[Construct]]를 가지고 있지 않다.

[[Construct]]를 가지고 있지 않는 non-constructor함수를 new 연산자와 함께 사용하면 오류가 발생하며, constructor함수를 new 연산자없이 호출하면 내부적으로 [[Call]]이, new 와 함께 호출하면 내부적으로 [[Construct]]가 호출된다.

// 함수 선언문
function f() =  {};

// 함수 표현식
const f = function() {};

// 화살표 함수
const f = () => {};

//메서드 축약 표현
const ob = {
  f() {},
};

new.target

위에서 언급했던 것처럼 생성자 함수는 일반 함수와 구분하기 위해서 첫 글자를 대문자로 쓰는 파스칼 케이스를 사용한다. 하지만 이러한 주의에도 불구하고 우리는 사람인지라 생성자 함수를 new 연산자를 빼먹고 사용하는 실수를 범할 수 있는데 이러한 실수를 잡아주기 위해 ES6에서는 new.target를 지원한다.

함수 내부에서 new.target을 사용하면 해당 함수가 호출될 때 new 연산자와 함께 호출되었다면 함수 자신을, 아니라면 undefined를 가리킨다. 따라서 다음과 같은 방법으로 해당 생성자 함수를 new를 통해 호출했든 안 했든 알맞게 호출되도록 만들 수 있다.

function Rectangle(width, height, color) = {
  if(!new.target) {
    return new Rectangle(width, height, color);  // new와 함께 호출되지 않았다면 new을 붙여 재귀적으로 다시 호출
  }
  this.width = width;
  this.height = height;
  this.color = color;
  this.getArea = function() {
    return this.width * this.height;
  };
}

대부분의 빌트인 생성자 함수들은 new 연산자와 함께 호출되었는지 확인 후 적절한 값을 반환한다. 하지만 String, Number, Boolean 생성자 함수는 new을 사용하지 않고 호출하면 각 값들을 문자열, 숫자, 불리언 값으로 변환해 반환하기 때문에 데이터 타입 변환에 쓰이기도 한다.

profile
I Will be Relaxed Person

0개의 댓글