常规 {...}
语法允许我们创建一个对象。但通常我们需要创建许多类似的对象,例如多个用户或菜单项等。
可以使用构造函数和 "new"
运算符来完成此操作。
构造函数
从技术上讲,构造函数是常规函数。不过有两个约定
- 它们以大写字母开头命名。
- 它们应该只使用
"new"
运算符执行。
例如
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
当一个函数用 new
执行时,它会执行以下步骤
- 创建一个新的空对象并将其分配给
this
。 - 函数体执行。通常它会修改
this
,向其添加新属性。 - 返回
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
运行。
如果我们有许多行代码都与创建单个复杂对象有关,我们可以将它们包装在一个立即调用的构造函数中,如下所示
// 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)
创建了一个具有给定 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
,并在结束时返回填充的this
。
我们可以使用构造函数来制作多个类似的对象。
JavaScript 为许多内置语言对象提供了构造函数:例如用于日期的 Date
,用于集合的 Set
以及我们计划学习的其他对象。
评论
<code>
标签,对于多行代码,请将其包装在<pre>
标签中,对于超过 10 行的代码,请使用沙箱(plnkr、jsbin、codepen…)