ES6 new.target를 활용한 생성자 호출 검증

ES5 환경에서 생성자 함수를 일반 함수처럼 호출하면 this가 전역 객체(window)를 가리켜 의도치 않은 동작이 발생했습니다. 이를 방지하기 위해 instanceof 연산자로 생성자 호출 여부를 판별하는 패턴이 널리 사용되었습니다.

ES5의 생성자 호출 검증 방식

function Member( name ){
    if ( this instanceof Member ) {
        this.nickname = name;
    } else {
        return new Member( name );
    }
}

Member.prototype.getNickname = function(){
    return this.nickname;
};

console.log( Member( 'devKim' ).getNickname() );      // devKim
console.log( new Member( 'devKim' ).getNickname() );    // devKim

위 방식은 instanceof 체크로 강제 변환을 수행하지만, 프로토타입 체인 조작 등으로 우회될 수 있는 한계가 있습니다.

ES6 new.target 메타프로퍼티

ES6에서는 생성자 호출 방식을 명확히 식별할 수 있는 new.target이 도입되었습니다.

호출 방식new.target
new 연산자 사용해당 생성자 함수 또는 클래스
일반 함수 호출undefined

undefined 기반 검증

function Member( name ){
    if ( new.target !== undefined ){
        this.nickname = name;
    } else {
        throw new TypeError( 'new 연산자 필수' );
    }
}

// Member( 'devKim' );  // TypeError 발생
console.log( new Member( 'devKim' ).nickname );  // devKim

생성자 직접 비교 방식

new.target은 실제 호출된 생성자를 참조하므로 엄격한 일치 비교도 가능합니다.

function Member( name ){
    if ( new.target === Member ) {
        this.nickname = name;
    } else {
        throw new TypeError( 'new 연산자 필수' );
    }
}

// Member( 'devKim' );  // TypeError 발생
console.log( new Member( 'devKim' ).nickname );  // devKim

클래스 환경에서의 응용

ES6 클래스 구문을 ES5로 구현하면서 new.target을 활용해 클래스 메서드의 new 호출까지 차단할 수 있습니다.

const Member = (function(){
    'use strict';
    
    const Member = function( name ){
        if ( new.target === undefined ){
            throw new TypeError( 'new 연산자로 인스턴스화 필요' );
        }
        this.nickname = name;
    };

    Object.defineProperty( Member.prototype, 'introduce', {
        value: function(){
            if ( new.target !== undefined ) {
                throw new TypeError( '메서드는 new로 호출 불가' );
            }
            return this.nickname;
        },
        enumerable: false,
        writable: true,
        configurable: true
    });

    return Member;
})();

console.log( new Member( 'devKim' ).introduce() );  // devKim
// Member( 'devKim' );  // TypeError
// new (new Member('x')).introduce();  // 메서드 new 호출 시 TypeError

new.target을 활용하면 상속 계층에서 실제 호출된 생성자를 추적하거나, 추상 클래스 패턴 구현 등 더 정교한 메타프로그래밍이 가능합니다.

태그: JavaScript ES6 new.target Constructor metaproperty

7월 4일 21:23에 게시됨