new.target

new.target 元属性可让你检测是否使用 new 运算符调用函数或构造函数。在使用 new 运算符调用的构造函数和函数中,new.target 返回对调用 new 的构造函数或函数的引用。在正常的函数调用中,new.targetundefined

¥The new.target meta-property lets you detect whether a function or constructor was called using the new operator. In constructors and functions invoked using the new operator, new.target returns a reference to the constructor or function that new was called upon. In normal function calls, new.target is undefined.

Try it

语法

¥Syntax

js
new.target

¥Value

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

¥new.target is guaranteed to be a constructable function value or undefined.

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

描述

¥Description

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

¥The new.target syntax consists of the keyword new, a dot, and the identifier target. Because new is a reserved word, not an identifier, this is not a property accessor, but a special expression syntax.

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

¥The new.target meta-property is available in all function/class bodies; using new.target outside of functions or classes is a syntax error.

示例

¥Examples

函数调用中的 new.target

¥new.target in function calls

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

¥In normal function calls (as opposed to constructor function calls), new.target is undefined. This lets you detect whether a function was called with new as a constructor.

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 in constructors

在类构造函数中,new.target 指的是被 new 直接调用的构造函数。如果构造函数位于父类中并且是从子构造函数委托的,也会出现这种情况。new.target 指向 new 被调用的类。例如,当使用 new B() 初始化 b 时,打印了 B 的名字;同样,在 a 的情况下,打印了 A 类的名称。

¥In class constructors, new.target refers to the constructor that was directly invoked by new. This is also the case if the constructor is in a parent class and was delegated from a child constructor. new.target points to the class that new was called upon. For example, when b was initialized using new B(), the name of B was printed; and similarly, in case of a, the name of class A was printed.

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"

new.target 使用 Reflect.construct()

¥new.target using Reflect.construct()

Reflect.construct() 或类之前,通常通过传递 this 的值并让基本构造函数改变它来实现继承。

¥Before Reflect.construct() or classes, it was common to implement inheritance by passing the value of this, and letting the base constructor mutate it.

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,因为没有 new 就无法调用 Map() 构造函数。

¥However, call() and apply() actually call the function instead of constructing it, so new.target has value undefined. This means that if Base() checks whether it's constructed with new, an error will be thrown, or it may behave in other unexpected ways. For example, you can't extend Map this way, because the Map() constructor cannot be called without new.

所有内置构造函数都是通过读取 new.target.prototype 直接构造新实例的整个原型链。因此,为了确保(1)Base 是用 new 构造的,并且(2)new.target 指向子类而不是 Base 本身,我们需要使用 Reflect.construct()

¥All built-in constructors directly construct the entire prototype chain of the new instance by reading new.target.prototype. So to make sure that (1) Base is constructed with new, and (2) new.target points to the subclass instead of Base itself, we need to use 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 子类化)。

¥Note: In fact, due to the lack of Reflect.construct(), it is not possible to properly subclass built-ins (like Error subclassing) when transpiling to pre-ES6 code.

但是,如果你正在编写 ES6 代码,则更喜欢使用类和 extends,因为它更具可读性且不易出错。

¥However, if you are writing ES6 code, prefer using classes and extends instead, as it's more readable and less error-prone.

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

规范

Specification
ECMAScript Language Specification
# sec-built-in-function-objects

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看

¥See also