생성자 함수

정수·2023년 3월 24일
0
post-thumbnail

객체를 생성하는 방법은 여러가지가 있습니다. 그 중 생성자 함수를 이용하여 객체를 생성하는 방법에 대해 알아볼 것입니다.
생성자 함수(constructor)new 연산자와 함께 호출하여 객체(인스턴스)를 생성하는 함수를 말합니다.

JavaScript는 Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise 등의 빌트인 생성자 함수를 제공합니다.

생성자 함수를 사용하지 않더라고 객체 리터럴를 통해 객체를 생성하는 방법이 더 간편합니다. 하지만 객체 리터럴에 의해 객체를 생성하게 될 경우 단 하나의 객체만 생성하기 때문에 동일한 프로퍼티를 갖는 객체들을 여러 개 생성해야 하는 경우에는 생성자 함수를 사용합니다.

인스턴스 생성 과정

생성자 함수가 수행하는 역할은 크게 2가지가 있습니다. 인스턴스를 생성하는 것과 이를 초기화 하는 것입니다.

function Circle(radius) {
  // 1. 암묵적으로 빈 객체(인스턴스)가 생성되고 this에 바인딩됩니다.
  console.log(this); // Circle {}
  
  // 2. 생성자 함수에 기술되어 있는 코드가 한 줄씩 실행되어 this에 바인딩되어 있는 인스턴스를 초기화 합니다.
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
  
  // 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환됩니다.
  // 이때, 다른 객체를 return하게 되면 명시된 객체가 반환되며 (e.g. return {};)
  // 원시 값을 return하게 되면 원시 값은 무시되며 암묵적으로 this가 반환됩니다. (e.g. return 300;)
}

이 때문에 생성자 함수 내부에서의 return 문은 함수의 기본 동작을 훼손하기 때문에 반드시 생략해야 합니다.

바인딩 (name binding)
바인딩이란 식별자와 값을 연결하는 과정을 의미합니다.
예를 들면, 변수 선언은 식별자(변수)와 확보된 메모리 공간의 주소를 연결(바인딩)하는 것이며, this 바인딩이란 this를 가리킬 객체와 this를 연결(바인딩)하는 것을 의미합니다.

내부 메서드

함수는 일반 객체와는 달리 호출 가능한 객체입니다. 일반 객체가 가지고 있는 내부 슬롯과 내부 메서드는 함수 객체도 가지고 있으며, 추가적으로 함수로서 동작(호출)하기 위해 함수 객체만을 위한 [[Environment]], [[FormalParameters]] 등의 내부 슬롯과, [[Call]], [[Construct]] 같은 내부 메서드를 추가로 지니고 있습니다.

Circle(); // 일반적인 함수로 호출할 때는 [[Call]]이 호출됩니다.
new Circle(); // 생성자 함수로 호출할 때는 [[Construct]]가 호출됩니다.

이러한 내부 메서드 [[Call]]을 갖는 함수 객체를 callable이라 하며, 내부 메서드 [[Construct]]을 갖는 함수 객체를 constructor, 그렇지 않는 함수 객체를 non-constructor라고 부릅니다.

모든 함수 객체는 일반적인 호출이 가능하지만 생성자로서 호출이 가능할 수도, 불가능할 수도 있습니다. 이러한 차이는 함수 정의 방식에 따라 나뉘게 됩니다.

constructor & non-constructor

  • constructor : 함수 선언문, 함수 표현식, 클래스
  • non-constructor : 메서드(ES6 메서드 축약 표현), 화살표 함수

함수 선언문과 함수 표현식은 여기에서, 메서드에 관해서는 이 글을 참고하시면 됩니다.

여기서 주의해야할 상황은 생성자 함수로 호출될 것을 기대하고 일반 함수에 new 연산자를 붙여 호출하는 상황이나, 그와 반대로 생성자 함수를 일반 함수처럼 호출하는 상황이다.

// case.01
function add(x, y) {
  return x + y;
}
const addInst = new add(); // 원시 값을 return 했을 때는 무시하고 this 출력
console.log(addInst); // add {}

// case.02
function createUser(name, role) {
  return { name, role };
}
const instUser = new createUser('jung', 'admin'); // 객체를 return 했을 때는 해당 객체 반환
console.log(instUser); // { name: 'jung', role: 'admin' }

// case.03
const circle = Circle(5); // 위에서 정의한 생성자 함수를 일반 함수로 호출
console(circle); // undefined
console(getDiameter()); // 10
circle.getDiameter(); // TypeError: Cannot read property 'getDiameter' of undefined
// Circle에서 정의한 this는 전역객체를 가리키게 되면서 radius 및 getDiameter()는 전역 객체의 프로퍼티와 메서드가 됩니다.

이렇듯 일반 함수와 생성자 함수는 특별한 형식적 차이가 없기 때문에 함수를 표기함에 있어 구별할 수 있어야 합니다. 일반적으로 생성자 함수는 첫 문자를 대문자로 기술하는 파스칼 케이스 컨벤션을 따르고 있습니다. 하지만 실수는 언제나 발생할 수 있기에 ES6에서는 new.target을 지원하게 되었습니다.

new.target

함수 내부의 new.targetnew 연산자와 함께 생성자 함수로 호출되면 함수 자신을 가리키게 되며, 일반 함수로 호출될 경우 undefined입니다.

이렇게 constructor인 모든 함수 내부에서 암묵적인 지역 변수(e.g. this)와 같이 사용되는 프로퍼티를 메타 프로퍼티라고 부릅니다.

function Circle(radius) {
  // new.target을 지원하지 않는 경우라면 "if (!(this instanceof Circle))"을 사용할 수 있습니다.
  if (!new.target) {
    return new Circle(radius);
  }

  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

빌트인 생성자 함수

  • ObjectFunction 생성자 함수는 new 연산자 없이 호출해도 new 연산자와 함께 호출했을 때와 동일하게 동작합니다.
  • String, Number, Boolean 생성자 함수는 객체를 생성하고, 일반 함수로 호출하면 문자열, 숫자, 불리언 값을 반환합니다.
const strObj = new String(123);
const str = String(123);

console.log(strObj); // String {'123'}
console.log(str); // '123'

console.log(typeof strObj) // object
console.log(typeof str) // string

본 게시글은 아래 도서의 내용을 참고하여 학습한 내용을 바탕으로 정리한 글입니다.
부적절한 내용이 있거나 내용 수정이 필요한 경우 댓글이나 이메일로 알려주시면 빠른 시일내로 정정하도록 하겠습니다.

모던 자바스크립트 Deep Dive, [이웅모 지음]
profile
해피한하루

0개의 댓글