모던 자바스크립트 Deep Dive - 생성자 함수에 의한 객체 생성(2)
내부 메서드 [[Call]] 과 [[Construct]]
함수 선언문 또는 함수 표현식으로 정의한 함수는 일반적인 함수로서 호출할 수 있는 것은 물론 생성자 함수로서 호출할 수 있다.
// 함수는 객체다
function foo() {}
// 함수는 객체이므로 프로퍼티를 소요할 수 있다.
foo.prop = 10;
// 함수는 객체이므로 메서드를 소유할 수 있다.
foo.method = function() {
console.log(this.prop);
};
foo.method(); // 10
함수는 객체이지만 일반 객체와는 다르다. 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다.
함수가 일반 함수로서 호출되면 함수 객체의 내무 메서드 [[Call]]이 호출되고 new 연산자와 함께 생성자 함수로서 호출되면 내부 메서드 [[Construct]]가 호출된다.
function foo () {}
// 일반적인 함수로서 호출: [[Call]]이 호출된다.
foo();
// 생성자 함수로서 호출: [[Construct]]가 호출된다.
new foo();
내부 메서드 [[Call]]을 갖는 함수 객체를 callable이라 하며, 내부 메서드 [[Construct]]를 갖는 함수 객체를 constructor, 갖지 않는 함수를 non-construct라고 부른다. callable은 호출할 수 있는 객체, 즉 함수를 말하며, constructor 는 생성자 함수로서 호출할 수 있는 함수, non-constructor는 객체를 생성자 함수로서 호출할 수 없는 함수를 의미한다.
constructor 와 non-constructor의 구분
자바스크립트 엔진은 어떻게 constructor와 non-constructor를 구분할까?
- constructor : 함수 선언문, 함수 표현식, 클래스(클래스도 함수다)
- non-constructor : 메서드(ES6 메서드 축약 표현), 화살표 함수
// 일반 함수 정의 : 함수 선언문, 함수 표현식
function foo() {}
const bar = function() {};
// 프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수다. 이는 메서드로 인정하지 않는다.
const baz = {
x : fun
};
// 일반 함수로 정의된 함수만이 constructor다.
new foo(); // -> foo {}
new bar(); // -> bar {}
new baz.x(); // -> x {}
// 화살표 함수 정의
const arrow = () => {};
new arrow(); // TypeError : arrow is not a constructor
// 메서드 정의 : ES6의 메서드 축약 표현만 메서드로 인정한다.
const obj = {
x() {}
};
new obj.x(); // TypeError: obj.x is not a constructor
new 연산자
일반 삼수와 생성자 함수에 특별한 형식적 차이는 없다. new 연산자와 함께 함수를 호출하면 해당 함수는 생성자 함수로 동작한다. new 연산자와 함께 함수를 호출하면 해당 함수는 생정자 함수로 동작한다. 다시 말해, 함수 객체의 내부 메서드 [[Call]]이 호출되는 것이 아니라 [[Constructor]] 가 호출된다. 단 new 연산자와 함께 호출하는 함수는 non-constructor가 아닌 constructor이어야 한다.
// 생성자 함수로서 정의하지 않은 일반 함수
function add(x, y) {
return x + y;
}
// 생성자 함수로서 정의하지 않은 일반 함수를 new 연산자와 함께 호출
let inst = new add();
// 함수가 객체를 반환하지 않있으므로 반환문이 무시된다. 따라서 빈 객체가 생성되어 반환된다.
console.log(inst); // {}
// 객체를 반환하는 일반 함수
function createUser(name, role) {
return { name, role };
}
// 일반 함수를 new 연산자와 함께 호출
inst = new createUser('Lee', 'admin');
// 함수가 생성한 객체를 반환한다.
console.log(inst); // {name: 'Lee', role: 'admin'}
함수 내부에서 new.target을 사용하면 new 연산자와 함께 생성자 함수로서 호출되었는지 확인할 수 있다. new 연산자와 함께 생성자 함수로서 호출되면 함수 내부의 new.target은 함수 자신을 가리킨다. new 연산자 없이 일반 함수로서 호출된 함수 내부의 new.target은 undefined 다.
// 생성자 함수
function circle(radius) {
// 이 함수가 new 연산자와 함께 호출되지 않았다면 new.target은 undefined다.
if(!new.target) {
// new 연산자와 함께 생성자 함수를 재귀 호출하여 생성된 인스턴스를 반환한다.
return new circle(radius);
}
this.radius = radiusl
this.getDiameter = function() {
return 2 * this.radius;
};
}
// new 연산자 없이 생성자 함수를 호출하여도 new.target을 통해 생성자 함수로서 호출된다.
const circle = Circle(5);
console.log(circle.getDiameter());