new.target
new.target 元属性允许你检测函数或构造函数是否使用了 new 操作符进行调用。在构造函数和使用 new 操作符调用的函数中,new.target 返回对 new 操作符所调用的构造函数或函数的引用。在普通的函数调用中,new.target 是 undefined。
试一试
function Foo() {
if (!new.target) {
throw new TypeError("calling Foo constructor without new is invalid");
}
}
try {
Foo();
} catch (e) {
console.log(e);
// Expected output: TypeError: calling Foo constructor without new is invalid
}
语法
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 是一个 保留字,而不是标识符,所以这不是一个 属性访问器,而是一种特殊的表达式语法。
new.target 元属性在所有函数/类主体中都可用;在函数或类之外使用 new.target 会导致语法错误。
示例
函数调用中的 new.target
在普通的函数调用中(与构造函数调用相对),new.target 是 undefined。这使你能够检测函数是否作为构造函数与 new 一起调用。
function Foo() {
if (!new.target) {
throw new Error("Foo() must be called with new");
}
console.log("Foo instantiated with new");
}
new Foo(); // Logs "Foo instantiated with new"
Foo(); // Throws "Foo() must be called with new"
构造函数中的 new.target
在类构造函数中,new.target 指向被 new 直接调用的构造函数。即使构造函数在父类中并从子构造函数委托而来,情况也是如此。new.target 指向被 new 调用的类。例如,当 b 使用 new B() 初始化时,打印的是 B 的名称;类似地,对于 a,打印的是类 A 的名称。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
const a = new A(); // Logs "A"
const b = new B(); // Logs "B"
使用 Reflect.construct() 的 new.target
在 Reflect.construct() 或类出现之前,通常通过传递 this 的值,并让基构造函数对其进行修改来实现在继承。
function Base() {
this.name = "Base";
}
function Extended() {
// Only way to make the Base() constructor work on the existing
// `this` value instead of a new object that `new` creates.
Base.call(this);
this.otherProperty = "Extended";
}
Object.setPrototypeOf(Extended.prototype, Base.prototype);
Object.setPrototypeOf(Extended, Base);
console.log(new Extended()); // Extended { name: 'Base', otherProperty: 'Extended' }
然而,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) {
// Call the base class constructor, but setting `new.target` to the subclass,
// so that the instance created has the correct prototype chain.
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 {
// The constructor is omitted because it's just the default one
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
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-built-in-function-objects |
浏览器兼容性
加载中…