new를 강제하는 패턴
생성자를 호출 할 때 new 를 빼먹게 되면 문법오류나 런타임에러는 발생하지 않지만 논리적인 오류가 생겨 예기치 못한 결과가 나올수 있다.
new를 빼먹으면 생성자 내부의 this가 전역 객체를 가리키게 되기 때문이다.
생성자 내부에 this.menber와 같은 코드가 있을 때 이 생성자를 new 없이 호출하게 되면 실제로는 전역 객체에 member라는 새로운 프로퍼티가 생성된다. 이 프로퍼티는 window.menber 또는 그냥 member를 통해 접근할 수 있다. 이런 동작 방식은 바람직 하지 않다.
// 생성자
function Waffle(){
this.tastes = "yummy";
}
// 새로운 객체
var good_morning = new Waffle();
console.log(typeof good_morning); //'object'
console.log(good_morning.tastes); //'yummy'
// 안티패턴
// 'new'를 빼먹을때
var good_morning = Waffle();
console.log(typeof good_morning); //'object'
console.log(window.tastes); //'yummy'
ECMAScript 5 에서는 이러한 동작 방식의 문제에 대한 해결책으로 스트릭트 모드 에서는 this가 전역 객체를 가리키지 않도록 했다.
명명 규칙
생성자 함수명의 첫글자는 대문자를 쓰고(MyConstructor), 일반적인 함수와 메서드의 첫글자는 소문자를 사용한다(myConstructor).
that 사용
생성자가 항상 생성자로 동작하도록 해주는 패턴을 살펴보자. this에 모든 멤버를 추가하는 대신, that에 모든 멤버를 추가한 후 that를 반환하는 것이다.
function Waffle(){
var that = {};
that.tastes = "yummy";
return that;
}
간단한 객체라면 that이라는 지역변수를 만들 필요도 없이 객체 리터럴을 통해 객체를 반환해도 좋다.
function Waffle(){
return {
tastes : "yummy"
}
}
위의 Waffle() 구현 중 어느 것을 사용해도 호출 방법과 상관없이 항상 객체가 반환된다.
var first = new Waffle(), second = Waffle();
console.log(first.tastes);
console.log(second.tastes);
위 패턴의 문제점도 있다. 프로토타입과의 연결고리를 잃어버리게 된다는 점이다. 즉 Waffle() 프로토타입에 추가한 멤버를 객체에서 사용할 수 없다.
스스로 호출하는 생성자
앞서 설명한 패턴의 문제점을 해결하고 인스턴스 객체에서 프로토타입의 프로퍼티들을 사용할 수 있게 하려면 다음 접근 방법을 고려하면 된다.
생성자 내부에서 this가 해당 생성자의 인스턴스인지를 확인하고 그렇지 않은 경우 new 와 함께 스스로를 재호출 하는 것이다.
function Waffle(){
if(!(this instanceof Waffle)){
return new Waffle();
}
this.tastes = "yummy";
}
Waffle.prototype.wantAnother = true;
// 호출 확인
var first = new Waffle(), second = Waffle();
console.log(first.tastes);
console.log(second.tastes);
console.log(first.wantAnother);
console.log(second.wantAnother);
인스턴스를 판별하는 또다른 범용적인 방법은 생성자 이름을 하드코딩하는 대신 arguments.callee와 비교하는 것이다.
if(!(this instanceof arguments.callee)){
return new arguments.callee();
** ES 5의 스트릭트 모드에서는 허용되지 않는다.