[모던 자바스크립트 튜토리얼] 4.5 new 연산자와 생성자 함수

개발견 배도르만·2023년 3월 17일
1
post-thumbnail

new 연산자와 생성자 함수

객체 리터럴 {...} 을 사용하면 객체를 쉽게 만들 수 있다. 하지만 유사한 객체를 여러 개 만들어야 할 때에는 'new' 연산자생성자 함수를 사용하는 것이 좋은 선택이 될 수 있다.

생성자 함수

생성자 함수(constructor function)와 일반 함수에 기술적인 차이는 없다. 다만 생성자 함수는 아래 두 관례를 따른다.

  1. 함수 이름의 첫 글자는 대문자로 시작합니다.
  2. 반드시 'new' 연산자를 붙여 실행합니다.

예시:

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("보라");

alert(user.name); // 보라
alert(user.isAdmin); // false

new User(...)를 써서 함수를 실행하면 아래와 같은 알고리즘이 동작한다.

  1. 빈 객체를 만들어 this에 할당
  2. 함수 본문 실행. this에 새로운 프로퍼티를 추가해 this를 수정합니다.
  3. this 반환

예시를 이용해 new User(...)가 실행되면 무슨 일이 일어나는지 살펴 보자.

function User(name) {
  // this = {};  (빈 객체가 암시적으로 만들어짐)

  // 새로운 프로퍼티를 this에 추가함
  this.name = name;
  this.isAdmin = false;

  // return this;  (this가 암시적으로 반환됨)
}

이제 let user = new User("보라")는 아래 코드를 입력한 것과 동일하게 동작한다.

let user = {
  name: "보라",
  isAdmin: false
};

new User("보라")이외에도 new User("호진"), new User("지민") 등을 이용하면 손쉽게 사용자 객체를 만들 수 있다. 객체 리터럴 문법으로 일일이 객체를 만드는 방법보다 훨씬 간단하고 읽기 쉽게 객체를 만드는 방법이다.

이처럼 생성자의 의의는 재사용할 수 있는 객체 생성 코드를 구현하는 것이다.

new를 붙여 실행한다면 어떤 함수든 생성자 함수가 될 수 있다. 함수명의 첫 글자가 대문자인 함수는 new를 붙여 실행해야 한다.

new function() { … }

재사용할 필요가 없는 복잡한 객체를 만들어야 하는 경우 아래와 같이 코드를 익명 생성자 함수로 감싸주는 방식을 사용할 수 있다.

let user = new function() {
  this.name = "John";
  this.isAdmin = false;

  // 사용자 객체를 만들기 위한 여러 코드.
  // 지역 변수, 복잡한 로직, 구문 등의
  // 다양한 코드가 여기에 들어갑니다.
};

위 생성자 함수는 익명 함수이기 때문에 어디에도 저장되지 않는다. 처음 만들 때부터 단 한 번만 호출할 목적으로 만들었기 때문에 재사용이 불가능하다. 이렇게 익명 생성자 함수를 이용하면 재사용은 막으면서 코드를 캡슐화 할 수 있다.

new.target과 생성자 함수

이 절의 내용은 자주 쓰이진 않는다.

new.target 프로퍼티를 사용하면 함수가 new와 함께 호출되었는지 아닌지를 알 수 있습니다.

new.target의 반환값

일반적인 방법으로 함수를 호출 시 undefined를 반환.
new와 함께 호출 시 함수 자체를 반환.

function User() {
  alert(new.target);
}

// 'new' 없이 호출함
User(); // undefined

// 'new'를 붙여 호출함
new User(); // function User { ... }

함수 본문에서 new.target을 사용하면 해당 함수가 new와 함께 호출되었는지(in constructor mode) 아닌지(in regular mode)를 확인할 수 있다.

이를 활용해 일반적인 방법으로 함수를 호출해도 new를 붙여 호출한 것과 같이 동작하도록 만든다면 아래와 같다.

function User(name) {
  if (!new.target) { // new 없이 호출해도
    return new User(name); // new를 붙여줍니다.
  }

  this.name = name;
}

let bora = User("보라"); // 'new User'를 쓴 것처럼 바꿔줍니다.
alert(bora.name); // 보라

이런 방식을 사용하면 new를 붙여 함수를 호출하든 아니든 코드가 동일하게 동작하기 때문에, 좀 더 유연하게 코드를 작성할 수 있다.

하지만 이 방법을 사용하여 new 없이 생성자 함수를 호출하는 경우 new를 생략하였기 때문에 코드가 정확히 무슨 일을 하는지 알기 어렵다. new가 새로운 객체를 만든다는 정보를 주기 때문에 new를 생략해서 객체를 만드는것은 정말 필요한 경우에만 사용하는 것이 좋다.

생성자와 return문

생성자 함수엔 보통 return 문이 없다. 반환해야 할 것들을 모두 this에 저장하여 this를 자동으로 반환하기 때문에 명시적인 return 문이 필요 없다.

하지만 return 문이 있다면 아래와 같은 간단한 규칙이 적용됩니다.

  • 객체를 return 한다면 this 대신 객체가 반환됩니다.
  • 원시형을 return 한다면 return문이 무시됩니다.
  • return 뒤에 객체가 오면 생성자 함수는 해당 객체를 반환해주고, 이 외의 경우는 this가 반환되죠.

아래 예시에선 첫 번째 규칙이 적용돼 return은 this를 무시하고 객체를 반환한다.

function BigUser() {

  this.name = "원숭이";

  return { name: "고릴라" };  // <-- this가 아닌 새로운 객체를 반환함
}
alert( new BigUser().name );  // 고릴라

아무것도 return하지 않는 예시에서는 undefined 즉, 원시형을 반환하는 경우이므로 두 번째 규칙이 적용된다.

function SmallUser() {

  this.name = "원숭이";

  return; // <-- this를 반환함
}

alert( new SmallUser().name );  // 원숭이

(return문이 있는 생성자 함수는 거의 없다.)

인수가 없는 생성자 함수는 괄호를 생략해 호출할 수 있다.

let user = new User; // <-- 괄호가 없음
// 아래 코드는 위 코드와 똑같이 동작합니다.
let user = new User();

명세서엔 괄호를 생략해도 된다고 정의되어 있지만, '좋은 스타일’은 아니다.

생성자 내 메서드

this에 프로퍼티 뿐만 아니라, 메서드를 더해주는 것도 가능하다.

아래 예시에서 new User(name)는 프로퍼티 name과 메서드 sayHi를 가진 객체를 만들어준다.

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "제 이름은 " + this.name + "입니다." );
  };
}

let bora = new User("이보라");

bora.sayHi(); // 제 이름은 이보라입니다.

/*
bora = {
   name: "이보라",
   sayHi: function() { ... }
}
*/

✍️ 정리

  • 생성자 함수는 일반 함수다.
  • 다른 함수와의 구분을 위해 함수명 첫 글자를 대문자로 작성한다.
  • 생성자 함수는 반드시 new 연산자와 함께 호출해야 한다.
  • new와 함께 호출하면 내부에서 this가 암시적으로 만들어지고, 마지막엔 this가 반환된다.
  • 생성자 함수는 유사한 객체를 여러 개 만들 때 유용하다.

자바스크립트는 언어 차원에서 다양한 생성자 함수를 제공한다. Date, Set, Math 등의 내장객체는 이러한 방법으로 만들 수 있다.

profile
네 발 개발 개

0개의 댓글