new.target

new.target 元属性允许您检测函数或构造函数是否使用 new 运算符调用。在使用 new 运算符调用的构造函数和函数中,new.target 返回对 new 被调用的构造函数或函数的引用。在普通函数调用中,new.targetundefined

试一试

语法

js
new.target

new.target 保证是一个可构造的函数值或 undefined

  • 在类构造函数中,它指的是 new 被调用的类,它可能是当前构造函数的子类,因为子类通过 super() 传递调用超类的构造函数。
  • 在普通函数中,如果函数是直接使用 new 构造的,则 new.target 指的是函数本身。如果函数在没有 new 的情况下被调用,则 new.targetundefined。函数可以用作 extends 的基类,在这种情况下,new.target 可能指的是子类。
  • 如果构造函数(类或函数)通过 Reflect.construct() 调用,则 new.target 指的是作为 newTarget 传递的值(默认为 target)。
  • 箭头函数 中,new.target 从周围的作用域继承。如果箭头函数没有定义在另一个具有 new.target 绑定 的类或函数中,则会抛出语法错误。
  • 静态初始化块 中,new.targetundefined

描述

new.target 语法由关键字 new、一个点和标识符 target 组成。因为 new 是一个 保留字,而不是标识符,所以这不是一个 属性访问器,而是一种特殊的表达式语法。

new.target 元属性在所有函数/类体中都可用;在函数或类外部使用 new.target 是语法错误。

示例

函数调用中的 new.target

在普通函数调用(与构造函数调用相反)中,new.targetundefined。这使您可以检测函数是否作为构造函数使用 new 调用。

js
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 的名称。

js
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 的值来实现继承,并让基本构造函数对其进行修改。

js
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()

js
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,因为它更易读且错误更少。

js
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 语言规范
# sec-built-in-function-objects

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅