原文链接:http://javascript.info/constructor-new,translate with ❤️ by zhangbao.

普通的 {…} 语法能实现一个对象的创建。但是我们经常需要创建多个类似的对象,像多个用户或者菜单项等。

这可以用构造函数和“new”操作符实现。

构造函数

构造函数在技术上就是个普通函数。但是有两个约定:

  1. 通常以大写字母开头。

  2. 使用“new”操作符调用。

例如:

  1. function User(name) {
  2. this.name = name;
  3. this.isAdmin = false;
  4. }
  5. let user = new User("Jack");
  6. alert(user.name); // Jack
  7. alert(user.isAdmin); // false

当一个函数使用 new User(…) 方式执行时,会执行一下步骤:

  1. 一个新的空对象被创建,赋值给 this。

  2. 函数体调用,通常做法是修改 this,在其上添加属性。

  3. 返回 this 的值。

也就是说,new User(…) 做了这些事情:

  1. function User(name) {
  2. // this = {}; (隐式的)
  3. // 向 this 添加属性
  4. this.name = name;
  5. this.isAdmin = false;
  6. // return this; (隐式的)
  7. }

因此 new User(‘Jack’) 的结果跟下面的对象一样:

  1. let user = {
  2. name: "Jack",
  3. isAdmin: false
  4. };

现在如果我们需要创建其他对象,可以调用 new User(‘Ann’),new User(‘Alice’) 等等。比每次使用字面量的形式更简短,更易读。

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

我们再次注意下——技术上讲,任何函数都可以作为构造函数使用。这是因为,任何函数都可以使用 new 的方式调用。然后都会执行上述的算法步骤。“首字母大写”是通用的约定,用来分清这是一个用 new 调用的函数,也就是构造函数。

new function() {…}

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

  1. let user = new function() {
  2. this.name = "John";
  3. this.isAdmin = false;
  4. // ...其他创建用户的代码
  5. // 可能是比较复杂的逻辑或者语句
  6. // 一些本地变量等
  7. };

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

双语法构造函数:new.target

高级内容

这个部分的语法很少使用,除非您想知道所有的内容,否则跳过它。

在函数内部,我们可以通过 new.target 属性,检查函数是不是用 new 调用的。

如果属于常规调用,这个属性值就返回空;如果使用 new 调用的,就返回函数本身。

  1. function User() {
  2. alert(new.target);
  3. }
  4. // 不用 "new":
  5. User(); // undefined
  6. // 用 "new":
  7. new User(); // function User { ... }

一个函数,既可以设计用来处理普通调用,也可以用来设计处理 new 方式调用:

  1. function User(name) {
  2. if (!new.target) { // 如果不是用 new 方式调用的
  3. return new User(name); // ...就改成是用 new 方式调用的
  4. }
  5. this.name = name;
  6. }
  7. let john = User("John"); // 将所有的调用转为 new User
  8. alert(john.name); // John

这种方式通常用在库中使用,提供了方便灵活的调用方式。这样大家可以使用/不使用 new 调用函数了,同样是可行的。

不过,在任何地方使用这种方式都不是一件好事,因为省略 new 会让它不那么明显。如果使用了 new,我们就知道,哦,原来是要创建一个新的对象。

在构造函数里 return

通常,构造函数里是没有 return 语句的。它们的任务就是向 this 上写东西,它就会自动变成结果返回。

但是如果有 return 语句的话,规则也很简单:

  • 如果 return 的是一个对象,那么调用结果就会返回对象而不是 this。

  • 如果 return 返回的是一个原始类型的值,就会被忽略。

也就是说,return 对象的时候就返回这个对象,否则返回的是 this。

例如,下例中,return 覆盖 this 返回了一个对象:

  1. function BigUser() {
  2. this.name = "John";
  3. return { name: "Godzilla" }; // <-- returns an object
  4. }
  5. alert( new BigUser().name ); // Godzilla, got that object ^^

这里有一个返回空的 return(或者我们可以在它后面放一个原始类型值,都一样)。

  1. function SmallUser() {
  2. this.name = "John";
  3. return; // 结束执行, 返回 this
  4. // ...
  5. }
  6. alert( new SmallUser().name ); // John

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

忽略圆括号

顺便说一下,我们可以忽略 new 之后的圆括号,如果不需要提供参数的话:

  1. let user = new User; // <-- 没有圆括号
  2. // 等同于
  3. let user = new User();

省略括号不被认为是“好的风格”,但是语法层面上是允许的。

构造函数里的方法

使用构造函数来创建对象会带来很大的灵活性。构造函数可能会提供参数用来定义如何构造对象,以及如何放入其中。

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

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

  1. function User(name) {
  2. this.name = name;
  3. this.sayHi = function() {
  4. alert( "My name is: " + this.name );
  5. };
  6. }
  7. let john = new User("John");
  8. john.sayHi(); // My name is: John
  9. /*
  10. john = {
  11. name: "John",
  12. sayHi: function() { ... }
  13. }
  14. */

总结

  • 构造函数,或者简称构造器,就是普通函数。我们对它有个约定,就是名字的首字母是大写的。

  • 构造函数是使用 new 调用的。这样的调用方式,会在开始创建一个空的 this,在最后呢,再返回一个被填充了的 this。

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

JavaScript 为许多内置的语言对象提供了构造函数:像日期对象 Date,集合对象 Set 和其他我们将要学习的对象。

对象,我们会回来的!

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

在我们了解到这一点之后,在章节《对象、类、继承》中,我们返回到对象并深入地讲解它们,包括继承和类。

(完)