在本教程中,您将了解 JavaScript 构造函数以及如何使用 new 关键词创建对象。

JavaScript 构造函数简介

JavaScript 对象教程中,您学习了如何使用对象字面量语法来创建新对象。

例如,以下代码创建了一个 person 对象,它有两个属性 firstNamelastName

let person = {
    firstName: 'John',
    lastName: 'Doe'
};

在实践中,您经常需要创建许多类似 person 对象的对象。

因此,您可以使用构造函数来自定义对象类型,并使用 new 运算符从自定义的类型创建多个对象。

从技术上讲,构造函数是具有以下约定的普通函数​​:

  • 构造函数的名称以大写字母开头 ,例如 Document,  Person 等。
  • 构造函数只能使用 new 运算符创建。
请注意,ES6 引入关键词 class 允许您定义自定义类型。类只是构造函数的语法糖,具有一些增强功能。

以下示例定义了一个构造函数,称为 Person

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

在此示例中,除了其名称以大写字母开头外,Person 与常规函数相同。要创建 Person 的实例,您可以使用 new 关键词:

let person = new Person('John','Doe');

基本上,new 关键词执行以下操作:

  • 创建一个新的对象并将其分配给 this 变量。
  • 将参数 'John''Doe' 分配给对象的 firstNamelastName 属性。
  • 返回 this 值。

它在功能上等同于以下内容:

function Person(firstName, lastName) {
    // this = {};

    // 添加属性到 this
    this.firstName = firstName;
    this.lastName = lastName;

    // return this;
}

因此,声明如下:

let person = new Person('John','Doe');

...返回与以下语句相同的结果:

let person = { firstName: 'John', lastName: 'Doe'}

但是,构造函数允许您创建多个与 Person 相似的对象。例如:

let person1 = new Person('Jane','Doe')
let person2 = new Person('James','Smith')

向 JavaScript 构造函数添加方法

对象可能具有操作其属性的方法。要向构造函数创建的对象添加方法,可以使用关键词 this。例如:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    this.getFullName = function () {
        return this.firstName + " " + this.lastName;
    };
}

现在,您可以创建一个新 Person 对象并调用 getFullName() 方法:

let person = new Person("John", "Doe");
console.log(person.getFullName());

输出:

John Doe

构造函数的问题在于,当您创建 Person 的多个实例时,this.getFullName() 在每个实例中都会复制一次,这不是内存高效的用法。

要解决此问题,您可以使用 JavaScript 原型,使自定义类型的所有实例都可以共享相同的方法。

从构造函数返回

通常,构造函数隐式地返回 this。但如果它有一个 return 声明,那么规则如下:

  • 如果return 后面返指定返回的是一个对象,则构造函数返回指定的对象而不是返回 this
  • 如果 return 后面返回不是对象,则会返回默认值 this。

不使用关键词 new 调用构造函数

从技术上讲,您可以像调用普通函数一样调用构造函数,而无需使用像这样使用 new 关键词:

let person = Person('John','Doe');

在这种情况下,Person 构造函数可以像普通函数一样调用。因此,Person 函数内部的 this 绑定的不是变量 person,而是全局对象

如果您尝试访问person对象的 firstName 或者 lastName 属性,您将收到错误消息 TypeError: Cannot read property 'firstName' of undefined:

console.log(person.firstName);

错误:

TypeError: Cannot read property 'firstName' of undefined

同样,您不能访问getFullName()方法,因为它绑定到全局对象。

person.getFullName();

错误:

TypeError: Cannot read property 'getFullName' of undefined

为了防止在没有关键词 new 的情况下调用构造函数,ES6 引入 new.target 属性。

如果使用关键词 new 调用构造函数,则 new.target 返回函数的引用。否则,它返回 undefined

下面在 Person 函数中添加语句 console.log(new.target);  向控制台打印 new.target

function Person(firstName, lastName) {
    console.log(new.target);

    this.firstName = firstName;
    this.lastName  = lastName;

    this.getFullName = function () {
        return this.firstName + " " + this.lastName;
    };
}

以下语句将会打印 undefined,因为 Person 构造函数像普通函数一样被调用:

let person = Person("John", "Doe");

输出:

undefined

但是,以下语句打印对函数的引用,因为它是使用 new 关键词调用 Person 构造函数:

let person = new Person("John", "Doe");

输出:

[Function: Person]

通过使用 new.target,您可以强制构造函数的调用者使用 new 关键词。否则,您可以会抛出这样的错误:

function Person(firstName, lastName) {
    if (!new.target) {
        throw Error("你必须使用 new 关键词调用");
    }

    this.firstName = firstName;
    this.lastName = lastName;
}

或者,如果用户没有使用 new 关键字调用构造函数,您可以自己在构造函数内部调用 Person 构造函数并返回,这使得构造函数更为强壮:

function Person(firstName, lastName) {
    if (!new.target) {
        return new Person(firstName, lastName);
    }

    this.firstName = firstName;
    this.lastName = lastName;
}

let person = Person("John", "Doe");

console.log(person.firstName);

这种模式常用于 JavaScript 库和框架中,使语法更加灵活。

结论

  • JavaScript 构造函数是用于创建多个相似对象的普通函数​​。