构造函数

基线 广泛可用

此功能已经很成熟,并在许多设备和浏览器版本中有效。自以下时间起,它在所有浏览器中可用 2017 年 3 月.

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

**注意:**此页面介绍了 constructor 语法。有关所有对象上存在的 constructor 属性,请参见 Object.prototype.constructor

试一试

语法

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

还有一些额外的语法限制

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

描述

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

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

如果您不提供自己的构造函数,则会为您提供一个默认构造函数。如果您的类是基类,则默认构造函数为空

js
constructor() {}

如果您的类是派生类,则默认构造函数会调用父构造函数,并将传递给它的任何参数一起传递

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

**注意:**显式构造函数(如上面的构造函数)与默认构造函数之间的区别在于后者实际上不通过 数组迭代器 通过 参数扩展 调用它。

这使得像这样的代码能够正常工作

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

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

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 对类进行调用会经历以下步骤

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

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

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 值。这个技巧被称为 "返回覆盖",它允许派生类的字段(包括 私有 字段)在不相关的对象上定义。

constructor 遵循正常的 方法 语法,因此可以全部使用 参数默认值rest 参数 等。

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

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

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 的成员必须是一个普通方法。

示例

使用构造函数

此代码片段取自 类示例 (实时演示)。

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

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

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

规范

规范
ECMAScript 语言规范
# sec-static-semantics-constructormethod

浏览器兼容性

BCD 表仅在启用 JavaScript 的浏览器中加载。

另请参阅