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의 스트릭트 모드에서는 허용되지 않는다.

사용자 정의 생성자 함수


객체 리터럴이나 내장 생성자 함수를 쓰지 않고 직접 생성자 함수를 만들어 객체를 생성할 수도 있다.

var Person = function(name){
this.name = name;
this.say = function(){
return "I am " + this.name;
};
};

var adam = new Person("adam");
console.log(adam.say()); // 'I am adam"


new와 함께 생성자 함수를 호출하면 함수 안에서 다음과 같은 일이 일어난다.

  • 빈 객체가 생성된다. 이 객체는 this 라는 변수로 참조할 수 있고, 해당 함수의 프로토타입을 상속 받는다.
  • this로 참조되는 객체에 프로퍼티와 메서드가 추가된다.
  • 마지막에 다른 객체가 명시적으로 반환되지 않을 경우, this 로 참조된 이 객체가 반환 된다.

즉 다음과 같이 진행 된다고 할 수 있다.

var Person = function(name){
// 객체 리터럴로 새로운 객체를 생성한다.
// var this = {};

// 프로퍼티와 메서드를 추가한다.
this.name = name;
this.say = function(){
return "I am " + this.name;
};

// this를 반환한다.
// return this;
};


이 예제에서는 간단히 say()라는 메서드를 this에 추가했다. 결과적으로 new Persion()을 호출할 때마다 메모리에 새로운 함수가 생성된다. say()라는 메서드는 인스턴스별로 달라지는게 아니므로 이런방식은 비효율적이므로 이 메서드는 Persion의 프로토타입에 추가하는 것이  더 낫다.


var Person = function(name){
this.name = name;
};

Person.prototype.say = function(){
return "I am " + this.name;
}

var adam = new Person("adam");
console.log(adam.say()); // 'I am adam"


생성자 내부의 this는 Persion의 프로토타입을 상속 받는다. 즉 다음 코드에 더 가깝다.


var this = Object.create(Persion.prototype);



생성자의 반환 값


함수 내에 return 문을 쓰지 않았더라도 생성자는 암묵적으로 this를 반환한다. 그러나 반환 값이 될 객체를 따로 정할 수도 있다. 다음 예제에서 새로운 객체를 생성하여 that으로 참조하고 반환하는 것을 볼 수 있다.

var Objectmarker = function(){
// 생성자가 다른 객체를 대신 반환하기로 결정했기 때문에
// 다음의 'name' 프로퍼티는 무시된다.
this.name = "this is it";

// 새로운 객체를 생성하여 반환한다.
var that = {};
that.name = "And that's that";
return that;
}

// 결과
var o = new Objectmarker();
console.log(o.name); // 'And that's that'


+ Recent posts