본문 바로가기
JavaScript & TypeScript

[javascript] new 연산자

by 대박플머 2016. 2. 23.

생성자란 new와 함께 호출될 뿐 별다를 것 없는 함수에 불과하다. 

그렇다면 생성자를 호출할 때 new를 빼먹으면 어떻게 될까? 문법 오류나 런타임 에러가 발생하지는 않지만, 논리적인 오류가 생겨 예기치 못한 결과가 나올 수 있다. new를 빼먹으면 생성자 내부의 this가 전역 객체를 가리키게 되기 때문이다.

생성자 내부에 this.member와 같은 코드가 있을 때 이 생성자를 new 없이 호출하면, 실제로는 전역 객체에 member라는 새로운 프로퍼티가 생성된다. 이 프로퍼티는 window.member 또는 그냥 member를 통해 접근할 수 있다. 알다시피 전역 네임스페이스는 항상 깨끗하게 유지해야 하기 때문에 이런 동작 방식은 대단히 바람직하지 않다. 

아래의 예제를 실행해보시면 정확하게 알 수 있을 것이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 생성자
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); // "undefined"
console.log(window.tastes); // "yummy"
cs

해결책 두가지가 있다. 

첫번째, 생성자가 항상 생성자로 동작하도록 해주는 방법이다. 

this에 모든 멤버를 추가하는 대신, that에 모든 멤버를 추가한 후 that을 반환하는 것이다. 

1
2
3
4
5
6
7
8
9
10
11
function Waffle(){
    var that = {};
    that.tastes = "yummy";
    return that;
}
 
function Waffle(){
    return {
        tastes : "yummy";
    };
}
cs

위의 Waffle() 구현 중 어느 것을 사용해도, 호출 방법과 상관 없이 항상 객체가 반환된다. 

1
2
3
4
var first = new Waffle(),
    second = Waffle();
console.log(first.tastes); // "yummy"
console.log(second.tastes); // "yummy"
cs

위의 방법은 좋은 방법이지만 한가지 단점은 프로토타입과의 연결고리를 잃어버린다는 것이다. 즉, Waffle() 프로토타입에 추가한 멤버를 객체에서 사용할 수 없다. 

두번째, 인스터스 객체 확인

생성자 내부에서 this가 해당 생성자의 인스턴스인지를 확인하고, 그렇지 않은 경우 new와 함께 스스로를 재호출하는 것이다. 

아래 예제를 보면 한번에 이해가 갈 것이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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); // "yummy"
console.log(second.tastes); // "yummy"
 
console.log(first.wantAnother); // true
console.log(second.wantAnother); // true
 
cs