본문 바로가기

자바스크립트🔥

ES6 / 헷갈리는 그 이름 this 3편 / 생성자 함수 호출

생성자 함수 호출

JS에서 생성자함수는 객체를 생성하는 역할을 함.

기존함수에 new 연산자를 붙이면 해당함수는 생성자 함수로 동작함.


여기서 잠깐~!

 

new연산자란 ?

"개발을 하다보면 유사한 객체를 여러개 만들어야할 상황이 온다.

new연산자와 생성자 함수를 사용하면 유사한 객체를 여러개 만들수 있다."라고 모질라 센세가 말했다.

{...}과 같은 객체 리터럴 문법을 사용해도 무방하지만, 일일이 객체를 만드는 것 보다 훨씬 간단하고 읽기 쉽게 객체를 만들 수 있다.

 

new연산자의 의의는 여기에 있다. 재사용할 수 있는 객체 생성 코드를 구현하는 것.

 


 

 

 

맨위에서 기존함수에 new 연산자를 붙이면 해당함수는 생성자 함수로 동작한다고 했는데,

반대로 생각해서 일반함수에 new연산자를 붙여 호출하면 생성자 함수처럼 동작가능함.

때문에 일반적으로 생성자함수명은 첫 문자를 대문자로하여 혼동을 방지해야함.

// 생성자 함수
function Person(name) {
  this.name = name;
}

var me = new Person('Lee');
console.log(me); // Person {name: "Lee"}

// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
var you = Person('Kim');
console.log(you); // undefined

new연산자와 함께 함수를 호출하면 this바인딩이 메서드나 함수호출때와는 다르게 동작함.

 

new연산자와 함께 생성자 함수를 호출하면 아래와 같은 수순으로 동작함.

  1. 빈 객체 생성 및 this 바인딩.
    생성자 함수를 호출하게 되면, 생성자 함수의 코드가 실행되기 전에 빈객체가 만들어진다.
    이 비어있는 상태의 객체가 생성자 함수가 새로 생성한 객체이다. 이 후에 생성자 함수에서 사용되는 this는 이 비어있는 객체를 가리킨다. 그리고 생성된 빈 객체는 생성자 함수의 prototype 프로퍼티가 가르키는 객체를 자신의 프로토타입 객체로 설정함. 
  2. this를 통한 프로퍼티 생성.
    생성된 빈 객체에 this를 사용하여 동적으로 프로퍼티나 메서드를 생성할 수 있다. this는 새로 생성된 객체를 가르키므로 this를 통해 생성된 프로퍼티나 메서드는 새로 생성된 객체에 추가됨.
  3. 생성된 객체 반환.
    반환문이 없는 경우, this에 바인딩 된 새로 생성한 객체가 반환됨. 명시적으로 this를 반환하여도 결과는 같음.
    반환문이 this가 아닌 다른 객체를 명시적으로 반환하는 경우에는 this가 아닌 명시된 해당 객체가 반환됨.
    이 때 this를 반환하지 않은 함수는 생성자 함수로서의 역할을 수행하지 못한다. 따라서 생성자 함수는 반환문을 명시적으로 사용하지 않는다.

 

객체 리터럴방식과 생성자 함수방식의 차이점은 프로토타입 객체에 있다.

// 객체 리터럴 방식
var foo = {
  name: 'foo',
  gender: 'male'
}

console.dir(foo);

// 생성자 함수 방식
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}

var me  = new Person('Lee', 'male'); // dir은 객체의 속성을 계층구조로 출력할 때 쓴다.
console.dir(me);

var you = new Person('Kim', 'female');
console.dir(you);
  • 객체 리터럴 방식의 경우 생성된 객체의 prototype 객체는 Object.prototype이다.
  • 생성자 함수 방식의 경우 생성된 객체의 prototype 객체는 Person.prototype이다.

생성자 함수에 new 연산자를 붙이지 않고 호출할 경우

일반함수와 생성자함수간에 특별한 형식상 차이는 없으며 new연산자를 붙이면 일반함수도 생성자함수로 동작한다.

 

그런데 !

객체 생성을 목적으로 한 생성자함수를 new 연산자 없이 호출하거나 일반함수에 new를 붙여 호출하면 오류가 발생할 수 있다.

이는 일반함수와 생성자함수의 this바인딩 방식이 다르기 때문이다.

 

일반함수의 this는 전역변수인 window에 바인딩 되고, new연산자와 함께 생성자함수를 호출하면 this는 생성된 빈 객체에 바인딩 된다.

function Person(name) {
  // new없이 호출하는 경우, 전역객체에 name 프로퍼티를 추가
  this.name = name;
};

// 일반 함수로서 호출되었기 때문에 객체를 암묵적으로 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체를 가리킨다.
var me = Person('Lee');

console.log(me); // undefined
console.log(window.name); // Lee

 

생성자 함수를 new없이 호출하면 함수Person 내부의 this는 전역객체를 가리키게 되고, name은 전역변수에 바인딩 된다. 또한 new와 함께 생성자 함수를 호출할 때 암묵적으로 반환하던 this도 반환하지 않으며 반환문이 없으므로 undefined를 반환한다.

 

일반함수와 생성자함수는 특별한 형식적 차이가 없기 때문에 일반적으로 생성자함수의 첫글자는 대문자로 표기하여 혼동을 방지하고자 한다. 그러나 이러한 노력에도 불구하고 실수가 발생할 수 있다.

 

이러한 위험성을 회피하기위해 사용되는 패턴(Object, Regex, Array)은 다음과 같다. 이 패턴은 다양한 라이브러리에서 광범위하게 사용된다.

 

참고로 대부분의 빌트인 생성자(Object, Regex, Array 등)는 new연산자와 함께 호출되었는지를 확인한 후 적절한 값을 반환한다.

 

// Scope-Safe Constructor Pattern
function A(arg) {
  // 생성자 함수가 new 연산자와 함께 호출되면 함수의 선두에서 빈객체를 생성하고 this에 바인딩한다.

  /*
  this가 호출된 함수(arguments.callee, 본 예제의 경우 A)의 인스턴스가 아니면 new 연산자를 사용하지 않은 것이므로 이 경우 new와 함께 생성자 함수를 호출하여 인스턴스를 반환한다.
  arguments.callee는 호출된 함수의 이름을 나타낸다. 이 예제의 경우 A로 표기하여도 문제없이 동작하지만 특정함수의 이름과 의존성을 없애기 위해서 arguments.callee를 사용하는 것이 좋다.
  */
  if (!(this instanceof arguments.callee)) {
    return new arguments.callee(arg);
  }

  // 프로퍼티 생성과 값의 할당
  this.value = arg ? arg : 0;
}

var a = new A(100);
var b = A(10);

console.log(a.value);
console.log(b.value);

arguments.callee 를 이해하려면 또 한세월 할 것 같아서

오늘은 여기까지 하려고한다.

 

마지막요약!!!!!

new연산자와함께 생성자함수를 호출하는 경우 생성자함수 내부의 this는 생성자 함수를 호출할 때 생성된 인스턴스를 가리킨다. 따라서 위에 코드 중 A가 new연산자와 함게 생성자 함수로써 호출되면 A함수 내부의 this는 생성자 함수에 의해 생성된 인스턴스를 가리킨다.

 

 

다음 글은 apply/call/bind