先记住这个答案
new.target 是 ES6 加的一个特殊语法,用来判断函数是怎么被调用的。用 new Foo() 调用时,Foo 里的 new.target 就是 Foo 这个函数本身;直接 Foo() 调用时,它是 undefined。在类的继承中,new Circle() 会先执行父类 Shape 的构造函数,这时 Shape 里读到的 new.target 是 Circle,也就是最初被 new 的那个类。箭头函数没有自己的 new.target,在箭头函数里读到的是包着它的那个普通函数的值。
new调用时指向被new的构造函数- 普通调用时是
undefined - 子类实例化时,父类构造函数里读到的是子类
- 箭头函数读的是外层函数的
new.target
三种情况跑一遍
普通函数的两种调用方式,加上类继承和箭头函数,放在一起看。
new 调用、继承JavaScriptfunction Legacy() {
console.log(new.target === Legacy ? 'new 调用' : '普通调用')
}
Legacy()
new Legacy()
class Shape {
constructor() {
const who = () => new.target.name // 箭头函数读外层 constructor 的 new.target
console.log(who())
}
}
class Circle extends Shape {}
new Shape()
new Circle() // 父类构造函数里看到的是 Circle查看输出与解释
普通调用
new 调用
Shape
Circlenew Circle() 时,Circle 没写构造函数,默认构造函数会调用 super(),于是执行了 Shape 的 constructor。这时 new.target 没有变成 Shape,而是一路保持为 Circle。箭头函数 who 自己没有 new.target,读到的就是这个 constructor 的值。
为什么要一路保持为子类?因为实例的原型要用 new.target.prototype 来设置。new Circle() 创建的实例原型必须是 Circle.prototype,才能用上子类的方法,所以父类构造函数得知道「最终要造的是谁」。
实际能拿它做什么
new.target 最常见的用法有两个,都是在构造函数开头加一个判断。
- 强制必须用
new,或者自动补上 - 老式构造函数忘写
new时,this会指向全局对象或undefined,属性就写错地方了。可以在开头判断if (!new.target) return new Legacy(...arguments),漏写new也能得到正确的实例。也可以直接抛错,提醒调用方。class不需要这么做,不写new调用class本来就会抛TypeError。 - 实现抽象基类
- JavaScript 没有
abstract关键字,可以在父类构造函数里判断if (new.target === Shape) throw new TypeError('Shape 不能直接实例化')。直接new Shape()会报错,new Circle()时new.target是Circle,正常通过。TypeScript 的abstract class只在编译时检查,编译后这个限制就没了,需要运行时也拦住时就用这个写法。
容易答错的地方
- 以为父类构造函数里的
new.target是父类 - 继承链上所有构造函数读到的都是最初被
new的那个类。想在父类里判断「是不是直接new我」,要比较new.target === Shape,而不是只看它是否存在。 - 以为
this instanceof Foo和new.target等价 this instanceof Foo能被骗过:Foo.call(Object.create(Foo.prototype))是普通调用,但this满足instanceof。new.target由调用方式决定,这种情况下它是undefined,判断更准确。
面试官还会怎么问?
在对象方法里读 new.target 会怎样?
不会报错,但永远是 undefined。方法简写本身不能被 new,也就不存在「被 new 调用」的时候。
Reflect.construct 和 new.target 有什么关系?
Reflect.construct(Target, args, NewTarget) 的第三个参数就是用来指定 new.target 的。执行 Target 的代码,但函数里读到的 new.target 是 NewTarget,实例的原型也取 NewTarget.prototype。在没有 class 语法时,想让老式构造函数正确继承内置类,就要靠它。
为什么箭头函数不能 new,却能读 new.target?
箭头函数不能当构造函数,所以它没有属于自己的 new.target。它读到的是外层普通函数的值,就像它读 this 和 arguments 一样。外层没有普通函数、箭头函数直接写在顶层时,读 new.target 是语法错误。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。