new.target
new.target
元属性允许你检测函数或构造函数是否是通过 new
运算符被调用的。在通过 new
运算符执行的函数或构造函数中,new.target
返回一个指向 new
调用的构造函数或函数的引用。在普通的函数调用中,new.target
的值是 undefined
。
尝试一下
语法
new.target
值
new.target
保证是一个可构造的函数值或 undefined
。
- 在类构造函数中,它指向
new
调用的类,这可能是当前构造函数的子类,因为子类通过super()
传递调用了父类的构造函数。 - 在普通函数中,如果函数是直接通过
new
构造的,则new.target
指向函数本身。如果函数不是通过new
调用的,则new.target
是undefined
。函数可以被用作extends
的基类,这种情况下new.target
可能指向子类。 - 如果构造函数(类或者函数)是通过
Reflect.construct()
调用的,那么new.target
指向作为newTarget
传递的值(默认为target
)。 - 在箭头函数中,
new.target
是从周围的作用域继承的。如果箭头函数不是在另一个具有new.target
绑定的类或函数中定义的,则会抛出语法错误。 - 在静态初始化块中,
new.target
是undefined
。
描述
示例
函数调用中的 new.target
构造函数中的 new.target
在类的构造函数中,new.target
指向直接被 new
执行的构造函数。如果构造函数位于父类中,并且是由子类的构造函数委托的,情况也是如此。new.target
指向被 new
所调用的类。例如,当通过 new B()
初始化 b
时,打印出了 B
的名称;类似地,对于 a
,打印出了类 A
的名称。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
const a = new A(); // 记录“A”
const b = new B(); // 记录“B”
使用 Reflect.construct() 的 new.target
在 Reflect.construct()
和类出现之前,通常通过传递 this
的值,并让基类构造函数对其进行更改来实现继承。
function Base() {
this.name = "基类";
}
function Extended() {
// 让 Base() 构造函数可在现有的 `this` 值上工作,
// 而不是在 `new` 创建的新对象上工作的唯一方法。
Base.call(this);
this.otherProperty = "子类";
}
Object.setPrototypeOf(Extended.prototype, Base.prototype);
Object.setPrototypeOf(Extended, Base);
console.log(new Extended()); // Extended { name: '基类', otherProperty: '子类' }
然而,call()
和 apply()
实际上是对函数进行调用而非构造,所以 new.target
的值是 undefined
。这意味着如果 Base()
检查它是否是通过 new
构造的,将会抛出错误(或者可能表现出其他意外的行为)。例如,你不能通过这种方式扩展 Map
,因为 Map()
构造函数不能在不使用 new
的情况下调用。
所有内置构造函数都通过读取 new.target.prototype
直接构造新实例的整个原型链。因此,为了确保(1)Base
是通过 new
构造的,以及(2)new.target
指向子类而不是 Base
本身,我们需要使用 Reflect.construct()
。
function BetterMap(entries) {
// 调用基类构造函数,但将 `new.target` 设置为子类,
// 因此创建的实例具有正确的原型链。
return Reflect.construct(Map, [entries], BetterMap);
}
BetterMap.prototype.upsert = function (key, actions) {
if (this.has(key)) {
this.set(key, actions.update(this.get(key)));
} else {
this.set(key, actions.insert());
}
};
Object.setPrototypeOf(BetterMap.prototype, Map.prototype);
Object.setPrototypeOf(BetterMap, Map);
const map = new BetterMap([["a", 1]]);
map.upsert("a", {
update: (value) => value + 1,
insert: () => 1,
});
console.log(map.get("a")); // 2
备注:实际上,由于缺少 Reflect.construct()
,在转译为 ES6 以前的代码时,无法正确地对内置对象进行子类化(例如 Error
子类化)。
但是,如果你正在编写 ES6 代码,最好使用类和 extends
,因为它更易读且更少出错。
class BetterMap extends Map {
// 构造函数被省略,因为它只是默认的
upsert(key, actions) {
if (this.has(key)) {
this.set(key, actions.update(this.get(key)));
} else {
this.set(key, actions.insert());
}
}
}
const map = new BetterMap([["a", 1]]);
map.upsert("a", {
update: (value) => value + 1,
insert: () => 1,
});
console.log(map.get("a")); // 2
规范
Specification |
---|
ECMAScript Language Specification # sec-built-in-function-objects |
浏览器兼容性
BCD tables only load in the browser