생성자 함수

황성호·2021년 2월 28일
1

네이밍 규칙

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

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

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

생성 알고리즘

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

생성시 발생하는 일

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

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

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

new function() { … }
재사용할 필요가 없는 복잡한 객체를 만들어야 한다고 해봅시다. 많은 양의 코드가 필요할 겁니다. 이럴 땐 아래와 같이 코드를 익명 생성자 함수로 감싸주는 방식을 사용할 수 있습니다.

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

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

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

new.target과 생성자 함수

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

일반적인 방법으로 함수를 호출했다면 new.target은 undefined를 반환합니다. 반면 new와 함께 호출한 경우엔 new.target은 함수 자체를 반환해줍니다.

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 john = User("John"); // 'new User'를 쓴 것처럼 바꿔줍니다.
alert(john.name); // John

라이브러리를 분석하다 보면 위와 같은 방식이 쓰인 걸 발견할 때가 있을 겁니다. 이런 방식을 사용하면 new를 붙여 함수를 호출하든 아니든 코드가 동일하게 동작하기 때문에, 좀 더 유연하게 코드를 작성할 수 있습니다.

그런데 new를 생략하면 코드가 정확히 무슨 일을 하는지 알기 어렵습니다. new가 붙어있으면 새로운 객체를 만든다는 걸 누구나 알 수 있는 반면에 말이죠. 이 방법은 정말 필요한 경우에만 사용하시고 남발하지 않으시길 바랍니다.

생성자와 return문

생성자 함수엔 보통 return 문이 없습니다. 반환해야 할 것들은 모두 this에 저장되고, this는 자동으로 반환되기 때문에 반환문을 명시적으로 써 줄 필요가 없습니다.

그런데 만약 return 문이 있다면 어떤 일이 벌어질까요? 아래와 같은 간단한 규칙이 적용됩니다.

객체를 return 한다면, this 대신 객체가 반환됩니다.
원시형을 return 한다면, return문이 무시됩니다.

return 뒤에 객체가 오면 생성자 함수는 해당 객체를 반환해주고, 이 외의 경우는 this가 반환되죠.

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

function BigUser() {

  this.name = "John";

  return { name: "Godzilla" };  // <-- this가 아닌 새로운 객체를 반환함
}

alert( new BigUser().name );  // Godzilla

아무것도 return하지 않는 예시를 살펴봅시다. 원시형을 반환하는 경우와 마찬가지로 두 번째 규칙이 적용됩니다.

function SmallUser() {

  this.name = "John";

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

alert( new SmallUser().name );  // John

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( "My name is: " + this.name );
  };
}

let john = new User("John");

john.sayHi(); // My name is: John

/*
john = {
   name: "John",
   sayHi: function() { ... }
}
*/
profile
개발!

0개의 댓글