前情提要

ES5及早期版本中函数具有多重功能,可以结合new使用,函数内的this值将指向一个新对象,在ES6中,函数混乱的双重身份有一些改变。

Javascript函数有两个不同的内部方法:[[Call]]和[[Construct]],当遇到通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上。否则将执行[[Call]]函数,从而直接执行代码中的函数体。具有[[Construct]]方法的函数被统称为构造函数。

注:不是所有函数都有construct方法,例如箭头函数,所以也不是所有方法都可以通过new来调用。

ES5判断函数被调用的方法

在ES5中要想确定一个函数是否通过new关键字被调用,最流行的方式是使用instanceof

function Person (name) {
    if(this instanceof Person){
        this.name = name
    } else {
        throw new Error('未通过new调用')
    }
}

//var person1 = new Person('Hello') //正常
var person2 = Person('Hello') //抛出异常

由于construct方法会创建一个Person实例,并将this绑定到新实例上,所以可以通过检查this值是否是构造函数的实例,如果是则代码可以正常运行,如果不是则抛出异常。

但这种方法并不是可靠的,再看一个例子

function Person (name) {
    if(this instanceof Person){
        this.name = name
    } else {
        throw new Error('未通过new调用')
    }
}
var person1 = new Person('Hello')
var person2 = Person.call(person1,'Hello') //正常执行

使用call方法将this设为person1的实例,对于函数本身,无法通过区分Person.call还是new关键字得到Person的实例。

ES6判断函数被调用的方法

ES6引入了new.target这个元属性。原属性是指非对象的属性。

描述:通常"new."的作用是提供属性访问的上下文,但这里"new."其实不是一个真正的对象。不过在构造方法调用中,new.target指向被new调用的构造函数,所以"new."成为了一个虚拟上下文。

当调用函数construct方法时,new.target被赋值为new操作符的目标,通常是新创建对象的实例。如果调用call方法,则new.target的值是Undefined。

function Person(name) {
    if(typeof new.target !== 'undefined') {
        this.name = name;
    } else {
        throw new Error('未通过new关键字调用')
    }
}

var person = new Person('Hello')
var Notperson = new Person.call('Hello') //抛出错误

也可以使用new.target是否被某个特定构造函数所调用

function Person(name) {
    if(new.target === Person)
    {
        this.name = name;
    } else {
        throw new Error('未使用new关键字调用')
    }
}

function AnotherPerson(name) {
    Person.call(this, name)
}

var person = new Person('Hello')
var anotherPerson = new AnotherPerson('Hello')

注:在函数外使用new.target是一个语法错误。

更多推荐

【Javascript】判断函数被调用的方法