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

这可以使用构造函数和 "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 User(...)执行时,它执行以下步骤:

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

换句话说,new User(...) 做类似的事情:

function User(name) {
  // this = {};(隐式创建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隐式返回)
}

所以 new User("Jack") 的结果是相同的对象:

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

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

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

让我们再次注意 — 从技术上讲,任何函数都可以用作构造函数。即:任何函数都可以运行 new,它会执行上面的算法。 “首字母大写”是一个共同的约定,以明确表示一个函数将被使用 new 运行。

new function() { … }

如果我们有许多关于创建单个复杂对象的代码行,我们可以将它们封装在构造函数中,如下所示:

let user = new function() {
  this.name = "John";
  this.isAdmin = false;

  // ...用户创建的其他代码
  // 也许是复杂的逻辑和陈述
  // 局部变量等
};

构造函数不能被再次调用,因为它不保存在任何地方,只是被创建和调用。所以这个技巧的目的是封装构建单个对象的代码,而不是将来重用。

双语法构造函数:new.target

在一个函数内部,我们可以使用 new.target 属性来检查它是用 new 还是不用它来调用。

常规调用为空,如果通过 new 调用,则等于函数:

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

// 不带 new:
User(); // undefined

// 带 new:
new User(); // function User { ... }

这可以使 new 和常规语法的工作原理相同:

function User(name) {
  if (!new.target) { // 如果你没有运行 new
    return new User(name); // ...会为你添加 new
  }

  this.name = name;
}

let john = User("John"); // 重新调用 new User
alert(john.name); // John

这种方法有时用在库中以使语法更加灵活。但因为省略 new 使得它不易阅读,这可不是一件好事。 而通过 new 我们可以都知道这个新对象正在创建。

构造函数 Return

通常,构造函数没有 return 语句。他们的任务是将所有必要的东西写入 this,并自动转换。

但是,如果有 return 语句,那么规则很简单:

  • 如果 return 对象,则返回它,而不是 this
  • 如果 return 一个原函数,则忽略。

换一种说法,带有对象的 return 返回该对象,在所有其他情况下返回 this

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

function BigUser() {

  this.name = "John";

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

alert( new BigUser().name );  // 哇哦,得到了对象,name 属性值为 Godzilla ^^

这里有一个 return 空的例子(或者我们可以在它之后放置一个原函数):

function SmallUser() {

  this.name = "John";

  return; // 完成执行,returns this

  // ...

}

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

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

Omitting parentheses

顺便说一下,如果没有参数我们 new 可以省略括号:

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

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

构造函数中的方法

使用构造函数来创建对象会带来很大的灵活性。通过构造函数的参数传递定义构造对象。

当然,我们不仅可以将属性添加到 this 中,而且还可以添加方法。

例如,new User(name) 下面用给定的 name 和方法 sayHi 创建一个对象:

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,并在最后返回填充的对象。

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

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

Objects, we’ll be back!

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

在我们了解了这一章之后 对象、类和继承 我们返回到对象并深入其中,包括继承和类。

任务

重要程度: 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

创建一个构造函数使用3种方法创建对象的 Calculator

  • read() 使用 prompt 请求两个值并在对象属性中记住它们。
  • sum() 返回这些属性的总和。
  • mul() 返回这些属性的乘积。

例如:

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

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

打开带有测试的沙箱。

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”存储在属性 value 中。起始值被设置为构造函数 startingValue 的参数。
  • read() 方法应该使用 prompt 来读取一个新的数字并将其添加到 value 中。

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

这里是代码的演示:

let accumulator = new Accumulator(1); // 初始 value 1
accumulator.read(); // 添加用户输入 value
accumulator.read(); // 添加用户输入 value
alert(accumulator.value); // 显示这些值的总和
运行 demo

打开带有测试的沙箱。

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);

使用沙箱的测试功能打开解决方案。

重要程度: 5

创建一个构造函数 Calculator 创建可扩展的 calculator 对象。

该任务由两部分组成。

  1. 首先,实现 calculate(str) 方法,“NUMBER operator NUMBER”(空格分隔),其格式为“1 + 2”,并返回结果。所以要实现加+和减-

    用法示例:

    let calc = new Calculator;
    
    alert( calc.calculate("3 + 7") ); // 10
  2. 然后添加 calculate 新操作的方法 addOperator(name, func)。它需要运算符 name 和实现它的双参数函数 func(a,b)

    例如,我们添加乘法*,除法/和求幂**

    let powerCalc = new Calculator;
    powerCalc.addMethod("*", (a, b) => a * b);
    powerCalc.addMethod("/", (a, b) => a / b);
    powerCalc.addMethod("**", (a, b) => a ** b);
    
    let result = powerCalc.calculate("2 ** 3");
    alert( result ); // 8
  • 此任务中没有括号或复杂的表达式。
  • 数字和运算符用一个空格分隔。
  • 添加错误处理。

打开带有测试的沙箱。

  • 请注意如何存储方法。它们只是添加到内部对象。
  • 所有测试和数值转换都在 calculate 方法中完成。将来它可能会扩展到支持更复杂的表达式。

使用沙箱的测试功能打开解决方案。

教程路线图

评论

在评论之前先阅读本内容…
  • 欢迎你在文章下添加补充内容、提出你的问题或回答提出的问题。
  • 使用 <code> 标签插入几行代码,对于多行代码 — 可以使用 <pre>,对于超过十行的代码 — 建议使用沙箱(plnkrJSBincodepen 等)。
  • 如果你无法理解文章中的内容 — 请详细说明。