출처 모던 자바스크립트 Deep Dive을 보고 정리한 내용입니다.
생성자 함수란 new 연산자와 함께 함수를 호출하여 객체(인스턴스)를 생성하는 것을 말한다. 객체 리터럴 외에 수많은 객체 생성 방법이 있다.
const obj = new Object();
obj.name = 'JKCho';
obj.foo = function() {console.log('hello');};
console.log(obj.name); //JKCho
obj.foo(); //hello
const strObj = new String('JK'); //Number, Boolean, Promise, Array 등의 빌트인 생성자 함수 또한 제공된다.
console.log(typeof strObj); //'object'
console.log(strObj); //String {'JK'}
const Func = new function('num', 'return num + num');
console.log(typeof func); //'function'
conole.log(func) //f anonymous(x)
이 방법은 그다지 유용하지 않아 객체 리터럴을 사용하는 것이 효율적일 것 같다.
const circle1 = {
radius: 1,
getDiameter() {
return 2 * this.radius;
}
};
const circle2 = {
radius: 5,
getDiameter() {
return 2 * this.radius;
}
};
객체 리터럴를 사용하여 객체를 생성하는 방법은 위 코드와 같이 중복된 함수 또는 프로퍼티 key를 포함할 경우 효율적이지 않다. 특히나 circle객체가 매우 많이 필요하게 될 경우 코드가 길어지고, 관리가 어려워질 것이다.
function Circle(radius) {
this.radius: radius;
this.getDiameter = function() {
return 2 * this.diameter;
};
}
const circle1 = new Circle(1);
const circle2 = new Circle(5);
생성자 함수를 사용하여 위 코드와 같이 간단하게 객체를 생성할 수 있다.
this: 자신의 프로퍼티나 메서드를 참조하기 위하 자기 참조 변수로, this바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
- 일반 함수로서 호출 : 전역 객체
- 메서드로서 호출 : 메서드를 호출한 객체
- 생성자 함수로서 호출 : 생성자 함수가 생성할 인스턴스
생성자 함수로 객체를 생성하는 방법은 new 연산자와 함께 함수를 호출하는 것이다. new 연산자 없이 함수를 호출하면 일반 함수로서 호출된다.
const circle3 = Circle(2);
console.log(circle3) //undefined, 일반함수 Circle은 반환문이 없다
console.log(radius) //2, 일반함수의 this는 전역객체를 가리킨다.
function Circle(radius) {
//Circle 인스턴스가 암묵적으로 생성되고, 생성될 인스턴스가 this에 바인딩된다.
console.log(this); //Circle {}
}
바인딩(binding) : 식별자와 객체를 연결하는 과정
function Circle(radius) {
//Circle 인스턴스가 암묵적으로 생성되고, 생성될 인스턴스가 this에 바인딩된다.
//this에 바인딩 되어있는 Circle 인스턴스를 초기화
this.radius: radius;
this.getDiameter = function() {
return 2 * this.diameter;
};
}
function Circle(radius) {
this.radius: radius;
this.getDiameter = function() {
return 2 * this.diameter;
};
}
//인스턴스 생성, 암묵적으로 this를 반환한다.
console.log(new Circle(5));// Circle {radius: 5, getDiameter: f}
function Circle2(radius) {
this.radius: radius;
this.getDiameter = function() {
return 2 * this.diameter;
};
return {} //빈 객체를 명시적으로 반환
}
console.log(new Circle(5));// {}, 명시적으로 반환한 빈 객체가 출력된다.
function Circle3(radius) {
this.radius: radius;
this.getDiameter = function() {
return 2 * this.diameter;
};
return 100 //원시값을 명시적으로 반환
}
console.log(new Circle(5));// Circle {radius: 5, getDiameter: f}, 원시값은 무시되고 this가 반환된다.
자바스크립트에서 함수는 객체이다. 그러나 일반 객체는 호출이 불가능하나 함수는 호출이 가능하다. 함수는 일반함수와 다르게 내부 슬롯인 [[Enviroment]], [[FormalParameters]]와 내부 메소드 [[Call]], [[Constructor]]를 갖기 때문이다. 일반함수로서 호출되면 [[Call]]이, 생성자 함수로서 호출되면 [[Constructor]]가 호출되어 동작한다.
모든 함수가 [[Constuctor]] 내부 메서드를 갖지는 않는다. [[Constructor]]를 갖는 함수를 constructor, 그렇지 못한 함수를 non-constructor라고 한다. 이는 함수를 어떻게 정의하였는지에 따라 결정된다.
함수가 생성자 함수로서 호출되었는지, 일반 함수로서 호출되었는지는 중요한 사항이다. 서로 다른 값을 반환하기 때문이다. 쉽게 이를 혼동할 수 있기 때문에 생성자 함수의 이름은 파스칼 컨벤션(Pascal Convention)으로 명명한다. 그럼에도 불구하고 위험에 노출되어 있는 것은 마찬가지이다. ES6부터는 new.target을 지원한다. 이를 통해 실수를 예방할 수 있다.
new.target은 생성자 함수로 호출되었을 때 함수 내부의 함수 자신을 가리킨다. 그러나 일반 함수로서 호출되었을 때는 undefined를 반환한다.
function Circle(radius) {
if(new.target === undefined){ //일반함수로서 호출되면 undefined를 반환한다.
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
}
}
const circle = Circle(5); //new 연산자 없이 호출
console.log(circle.getDiameter()); //10
ES5 이전 버전은 스코프 세이프 생성자 패턴 통해 위와 같이 동작할 수 있다.
function Circle(radius) {
if(!(this instanceof Circle)){
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function() {
return 2 * this.radius;
}
}
const circle = Circle(5); //new 연산자 없이 호출
console.log(circle.getDiameter()); //10
빌트인 생성자 함수는 내부적으로 위와 같이 구현되어 있어 new연산자 없이 사용이 가능하다. 그러나 Object와 Function와 같은 생성자 함수를 제외한 빌트인 생성자 함수(String, Number 등)는 new 연산자 없이 호출하면 객체가 아닌 해당 타입이 반환된다.
const str = String('hello');
console.log(typeof str); //'string'