构造函数

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2017.

constructor 方法是 class 的一个特殊方法,用于创建和初始化该类的对象实例。

¥The constructor method is a special method of a class for creating and initializing an object instance of that class.

注意:本页介绍 constructor 语法。有关所有对象上存在的 constructor 属性,请参阅 Object.prototype.constructor

¥Note: This page introduces the constructor syntax. For the constructor property present on all objects, see Object.prototype.constructor.

Try it

语法

¥Syntax

js
constructor() { /* … */ }
constructor(argument0) { /* … */ }
constructor(argument0, argument1) { /* … */ }
constructor(argument0, argument1, /* …, */ argumentN) { /* … */ }

还有一些额外的语法限制:

¥There are some additional syntax restrictions:

  • 名为 constructor 的类方法不能是 gettersetterasyncgenerator
  • 一个类不能有多个 constructor 方法。

描述

¥Description

构造函数使你能够提供在实例化对象上调用任何其他方法之前必须完成的任何自定义初始化。

¥A constructor enables you to provide any custom initialization that must be done before any other methods can be called on an instantiated object.

js
class Person {
  constructor(name) {
    this.name = name;
  }

  introduce() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const otto = new Person("Otto");

otto.introduce(); // Hello, my name is Otto

如果你不提供自己的构造函数,那么将为你提供默认构造函数。如果你的类是基类,则默认构造函数为空:

¥If you don't provide your own constructor, then a default constructor will be supplied for you. If your class is a base class, the default constructor is empty:

js
constructor() {}

如果你的类是派生类,则默认构造函数将调用父构造函数,并传递提供的任何参数:

¥If your class is a derived class, the default constructor calls the parent constructor, passing along any arguments that were provided:

js
constructor(...args) {
  super(...args);
}

注意:像上面这样的显式构造函数和默认构造函数之间的区别在于,后者实际上并不调用 数组迭代器争论蔓延

¥Note: The difference between an explicit constructor like the one above and the default constructor is that the latter doesn't actually invoke the array iterator through argument spreading.

这使得这样的代码能够工作:

¥That enables code like this to work:

js
class ValidationError extends Error {
  printCustomerMessage() {
    return `Validation failed :-( (details: ${this.message})`;
  }
}

try {
  throw new ValidationError("Not a valid phone number");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.name); // This is Error instead of ValidationError!
    console.log(error.printCustomerMessage());
  } else {
    console.log("Unknown error", error);
    throw error;
  }
}

ValidationError 类不需要显式构造函数,因为它不需要执行任何自定义初始化。然后,默认构造函数负责根据给定的参数初始化父级 Error

¥The ValidationError class doesn't need an explicit constructor, because it doesn't need to do any custom initialization. The default constructor then takes care of initializing the parent Error from the argument it is given.

但是,如果你提供自己的构造函数,并且你的类派生自某个父类,则必须使用 super() 显式调用父类构造函数。例如:

¥However, if you provide your own constructor, and your class derives from some parent class, then you must explicitly call the parent class constructor using super(). For example:

js
class ValidationError extends Error {
  constructor(message) {
    super(message); // call parent class constructor
    this.name = "ValidationError";
    this.code = "42";
  }

  printCustomerMessage() {
    return `Validation failed :-( (details: ${this.message}, code: ${this.code})`;
  }
}

try {
  throw new ValidationError("Not a valid phone number");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.name); // Now this is ValidationError!
    console.log(error.printCustomerMessage());
  } else {
    console.log("Unknown error", error);
    throw error;
  }
}

在类上使用 new 需要经过以下步骤:

¥Using new on a class goes through the following steps:

  1. (如果是派生类)评估 super() 调用之前的 constructor 主体。该部分不应访问 this,因为它尚未初始化。
  2. (如果是派生类)评估 super() 调用,它通过相同的过程初始化父类。
  3. 当前类的 fields 已初始化。
  4. 评估 super() 调用后的 constructor 主体(或整个主体,如果它是基类)。

constructor 主体内,你可以访问通过 this 创建的对象,并访问通过 newnew.target 调用的类。请注意,在执行 constructor 之前,方法(包括 getterssetters)和 原型链 已经在 this 上初始化,因此你甚至可以从超类的构造函数访问子类的方法。但是,如果这些方法使用 this,则 this 将尚未完全初始化。这意味着读取派生类的公共字段将导致 undefined,而读取私有字段将导致 TypeError

¥Within the constructor body, you can access the object being created through this and access the class that is called with new through new.target. Note that methods (including getters and setters) and the prototype chain are already initialized on this before the constructor is executed, so you can even access methods of the subclass from the constructor of the superclass. However, if those methods use this, the this will not have been fully initialized yet. This means reading public fields of the derived class will result in undefined, while reading private fields will result in a TypeError.

js
new (class C extends class B {
  constructor() {
    console.log(this.foo());
  }
} {
  #a = 1;
  foo() {
    return this.#a; // TypeError: Cannot read private member #a from an object whose class did not declare it
    // It's not really because the class didn't declare it,
    // but because the private field isn't initialized yet
    // when the superclass constructor is running
  }
})();

constructor 方法可以有返回值。虽然基类可以从其构造函数返回任何内容,但派生类必须返回一个对象或 undefined,否则将抛出 TypeError

¥The constructor method may have a return value. While the base class may return anything from its constructor, the derived class must return an object or undefined, or a TypeError will be thrown.

js
class ParentClass {
  constructor() {
    return 1;
  }
}

console.log(new ParentClass()); // ParentClass {}
// The return value is ignored because it's not an object
// This is consistent with function constructors

class ChildClass extends ParentClass {
  constructor() {
    return 1;
  }
}

console.log(new ChildClass()); // TypeError: Derived constructors may only return object or undefined

如果父类构造函数返回一个对象,则该对象将用作定义派生类的 类字段this 值。这个技巧称为 "返回覆盖",它允许派生类的字段(包括 private 字段)在不相关的对象上定义。

¥If the parent class constructor returns an object, that object will be used as the this value on which class fields of the derived class will be defined. This trick is called "return overriding", which allows a derived class's fields (including private ones) to be defined on unrelated objects.

constructor 遵循正常的 method 语法,因此 参数默认值其余参数 等都可以使用。

¥The constructor follows normal method syntax, so parameter default values, rest parameters, etc. can all be used.

js
class Person {
  constructor(name = "Anonymous") {
    this.name = name;
  }
  introduce() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person = new Person();
person.introduce(); // Hello, my name is Anonymous

构造函数必须是字面名称。计算属性 不能成为构造函数。

¥The constructor must be a literal name. Computed properties cannot become constructors.

js
class Foo {
  // This is a computed property. It will not be picked up as a constructor.
  ["constructor"]() {
    console.log("called");
    this.a = 1;
  }
}

const foo = new Foo(); // No log
console.log(foo); // Foo {}
foo.constructor(); // Logs "called"
console.log(foo); // Foo { a: 1 }

禁止调用异步方法、生成器方法、访问器和类字段 constructor。私有名字不能称为 #constructor。任何名为 constructor 的成员都必须是普通方法。

¥Async methods, generator methods, accessors, and class fields are forbidden from being called constructor. Private names cannot be called #constructor. Any member named constructor must be a plain method.

示例

¥Examples

使用构造函数

¥Using the constructor

此代码片段取自 类样本 (现场演示)。

¥This code snippet is taken from the classes sample (live demo).

js
class Square extends Polygon {
  constructor(length) {
    // Here, it calls the parent class' constructor with lengths
    // provided for the Polygon's width and height
    super(length, length);
    // NOTE: In derived classes, `super()` must be called before you
    // can use `this`. Leaving this out will cause a ReferenceError.
    this.name = "Square";
  }

  get area() {
    return this.height * this.width;
  }

  set area(value) {
    this.height = value ** 0.5;
    this.width = value ** 0.5;
  }
}

在绑定到不同原型的构造函数中调用 super

¥Calling super in a constructor bound to a different prototype

super() 调用当前类原型的构造函数。如果你更改当前类本身的原型,super() 将调用新原型的构造函数。更改当前类的 prototype 属性的原型不会影响 super() 调用哪个构造函数。

¥super() calls the constructor that's the prototype of the current class. If you change the prototype of the current class itself, super() will call the constructor that's the new prototype. Changing the prototype of the current class's prototype property doesn't affect which constructor super() calls.

js
class Polygon {
  constructor() {
    this.name = "Polygon";
  }
}

class Rectangle {
  constructor() {
    this.name = "Rectangle";
  }
}

class Square extends Polygon {
  constructor() {
    super();
  }
}

// Make Square extend Rectangle (which is a base class) instead of Polygon
Object.setPrototypeOf(Square, Rectangle);

const newInstance = new Square();

// newInstance is still an instance of Polygon, because we didn't
// change the prototype of Square.prototype, so the prototype chain
// of newInstance is still
//   newInstance --> Square.prototype --> Polygon.prototype
console.log(newInstance instanceof Polygon); // true
console.log(newInstance instanceof Rectangle); // false

// However, because super() calls Rectangle as constructor, the name property
// of newInstance is initialized with the logic in Rectangle
console.log(newInstance.name); // Rectangle

规范

Specification
ECMAScript Language Specification
# sec-static-semantics-constructormethod

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看