2022 年 10 月 1 日

构造函数,运算符“new”

常规 {...} 语法允许我们创建一个对象。但通常我们需要创建许多类似的对象,例如多个用户或菜单项等。

可以使用构造函数和 "new" 运算符来完成此操作。

构造函数

从技术上讲,构造函数是常规函数。不过有两个约定

  1. 它们以大写字母开头命名。
  2. 它们应该只使用 "new" 运算符执行。

例如

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

当一个函数用 new 执行时,它会执行以下步骤

  1. 创建一个新的空对象并将其分配给 this
  2. 函数体执行。通常它会修改 this,向其添加新属性。
  3. 返回 this 的值。

换句话说,new User(...) 执行类似于

function User(name) {
  // this = {};  (implicitly)

  // add properties to this
  this.name = name;
  this.isAdmin = false;

  // return this;  (implicitly)
}

因此 let user = new User("Jack") 给出的结果与

let user = {
  name: "Jack",
  isAdmin: false
};

现在,如果我们想创建其他用户,我们可以调用 new User("Ann")new User("Alice") 等。比每次使用文本短得多,而且也容易阅读。

这就是构造函数的主要目的——实现可重用的对象创建代码。

让我们再次注意——从技术上讲,任何函数(箭头函数除外,因为它们没有 this)都可以用作构造函数。它可以用 new 运行,并且它将执行上述算法。“首字母大写”是一种约定,为了明确一个函数要使用 new 运行。

new function() { … }

如果我们有许多行代码都与创建单个复杂对象有关,我们可以将它们包装在一个立即调用的构造函数中,如下所示

// create a function and immediately call it with new
let user = new function() {
  this.name = "John";
  this.isAdmin = false;

  // ...other code for user creation
  // maybe complex logic and statements
  // local variables etc
};

此构造函数无法再次调用,因为它没有保存在任何地方,只是创建并调用。因此,此技巧旨在封装构造单个对象的代码,而无需将来重用。

构造函数模式测试:new.target

高级内容

本节中的语法很少使用,除非你想了解所有内容,否则可以跳过。

在函数内部,我们可以使用特殊 new.target 属性检查它是否使用 new 调用或没有使用 new 调用。

对于常规调用,它是未定义的,对于使用 new 调用的函数,它等于该函数

function User() {
  alert(new.target);
}

// without "new":
User(); // undefined

// with "new":
new User(); // function User { ... }

可以在函数内部使用它来了解它是否使用 new 调用(“在构造函数模式中”)或没有使用 new 调用(“在常规模式中”)。

我们还可以同时进行 new 和常规调用以执行相同操作,如下所示

function User(name) {
  if (!new.target) { // if you run me without new
    return new User(name); // ...I will add new for you
  }

  this.name = name;
}

let john = User("John"); // redirects call to new User
alert(john.name); // John

这种方法有时用于库中,以使语法更灵活。这样,人们可以使用或不使用 new 调用函数,并且它仍然有效。

不过,这可能并不是一个到处都适用的好方法,因为省略 new 会让人不太清楚发生了什么。使用 new,我们都知道正在创建新对象。

从构造函数返回

通常,构造函数没有 return 语句。它们的任务是将所有必要的内容写入 this,它会自动成为结果。

但是,如果存在 return 语句,那么规则很简单

  • 如果 return 使用对象调用,那么将返回该对象,而不是 this
  • 如果 return 使用基元调用,那么它将被忽略。

换句话说,使用对象返回 return 会返回该对象,在所有其他情况下都会返回 this

例如,这里 return 通过返回一个对象来覆盖 this

function BigUser() {

  this.name = "John";

  return { name: "Godzilla" };  // <-- returns this object
}

alert( new BigUser().name );  // Godzilla, got that object

这里有一个带有空 return 的示例(或者我们可以在它后面放置一个基元,这并不重要)

function SmallUser() {

  this.name = "John";

  return; // <-- returns this
}

alert( new SmallUser().name );  // John

通常,构造函数没有 return 语句。我们在这里主要为了完整性而提到了返回对象的特殊行为。

省略括号

顺便说一下,我们可以在 new 后面省略括号

let user = new User; // <-- no parentheses
// same as
let user = new User();

在这里省略括号不被认为是“好风格”,但规范允许这种语法。

构造函数中的方法

使用构造函数创建对象提供了很大的灵活性。构造函数可能具有定义如何构造对象以及在其中放入什么内容的参数。

当然,我们不仅可以向 this 添加属性,还可以添加方法。

例如,下面的 new User(name) 创建了一个具有给定 namesayHi 方法的对象

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "My name is: " + this.name );
  };
}

let john = new User("John");

john.sayHi(); // My name is: John

/*
john = {
   name: "John",
   sayHi: function() { ... }
}
*/

为了创建复杂的对象,有一个更高级的语法,,我们将在后面介绍。

总结

  • 构造函数或简称为构造函数是常规函数,但有一个共同的约定,即用大写字母开头命名它们。
  • 构造函数只能使用 new 调用。这样的调用意味着在开始时创建空的 this,并在结束时返回填充的 this

我们可以使用构造函数来制作多个类似的对象。

JavaScript 为许多内置语言对象提供了构造函数:例如用于日期的 Date,用于集合的 Set 以及我们计划学习的其他对象。

对象,我们还会回来!

在本章中,我们只介绍了有关对象和构造函数的基础知识。它们对于在下一章中了解有关数据类型和函数的更多信息至关重要。

在学习完这些知识后,我们将回到对象,并在 原型、继承 章节中深入介绍它们。

任务

重要性:2

是否可以创建函数 AB,使得 new A() == new B()

function A() { ... }
function B() { ... }

let a = new A();
let b = new B();

alert( a == b ); // true

如果可以,请提供它们的代码示例。

可以。

如果函数返回一个对象,则 new 会返回该对象,而不是 this

因此,它们可以返回相同的外部定义对象 obj

let obj = {};

function A() { return obj; }
function B() { return obj; }

alert( new A() == new B() ); // true
重要性:5

创建一个构造函数 Calculator,它创建具有 3 个方法的对象

  • read() 提示输入两个值,并将它们分别保存为具有名称 ab 的对象属性。
  • sum() 返回这些属性的总和。
  • mul() 返回这些属性的乘积。

例如

let calculator = new Calculator();
calculator.read();

alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );

运行演示

打开包含测试的沙箱。

function Calculator() {

  this.read = function() {
    this.a = +prompt('a?', 0);
    this.b = +prompt('b?', 0);
  };

  this.sum = function() {
    return this.a + this.b;
  };

  this.mul = function() {
    return this.a * this.b;
  };
}

let calculator = new Calculator();
calculator.read();

alert( "Sum=" + calculator.sum() );
alert( "Mul=" + calculator.mul() );

在沙箱中打开包含测试的解决方案。

重要性:5

创建一个构造函数 Accumulator(startingValue)

它创建的对象应

  • 将“当前值”存储在属性 value 中。起始值设置为构造函数 startingValue 的参数。
  • read() 方法应使用 prompt 读取一个新数字并将其添加到 value 中。

换句话说,value 属性是所有用户输入值与初始值 startingValue 的总和。

以下是代码演示

let accumulator = new Accumulator(1); // initial value 1

accumulator.read(); // adds the user-entered value
accumulator.read(); // adds the user-entered value

alert(accumulator.value); // shows the sum of these values

运行演示

打开包含测试的沙箱。

function Accumulator(startingValue) {
  this.value = startingValue;

  this.read = function() {
    this.value += +prompt('How much to add?', 0);
  };

}

let accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert(accumulator.value);

在沙箱中打开包含测试的解决方案。

教程地图

评论

在评论之前请阅读此内容…
  • 如果您有改进建议,请 提交 GitHub 问题或提交拉取请求,而不是发表评论。
  • 如果您无法理解文章中的某些内容,请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行代码,请将其包装在 <pre> 标签中,对于超过 10 行的代码,请使用沙箱(plnkrjsbincodepen…)