在本教程中,您将学习 JavaScript 构造函数以及如何使用 new
关键字创建对象。
在 JavaScript 对象教程中,您学习了如何使用对象字面量语法创建新对象。例如,以下代码创建了一个具有两个属性 firstName
和 lastName
的新 person 对象:
1 2 3 4 5 |
let person = { firstName: 'John', lastName: 'Doe' }; |
在实际应用中,您经常需要创建许多类似于 person
对象的对象。
为此,您可以使用构造函数定义自定义类型,并使用 new
运算符从该类型创建多个对象。
一、JavaScript 构造函数简介
什么是构造函数?
构造函数(Constructor Function)是用来创建多个相似对象的函数。通过 new
关键字调用时,它会返回一个新的对象,并自动设置好 this
和原型。
通过构造函数,我们可以批量创建具有相同属性和方法的对象实例。
它本质上是一个普通的函数,但当使用 new
关键字调用它时,会发生以下特性行为:
特性行为:
- 创建一个新的空对象;
- 将
this
指向这个新对象; - 执行函数体内的代码(通常用于设置属性);
- 默认返回这个新对象(除非手动返回一个对象)。
构造函数的定义语法
构造函数的命名通常首字母大写,以示区别。
1 2 3 4 5 6 7 |
function Person(name, age) { this.name = name; this.age = age; this.sayHello = function() { console.log("Hi, I'm " + this.name); }; } |
Person
是构造函数名(建议首字母大写以示区分);this.name
和this.age
表示将传入的参数赋值给新对象的属性;this.sayHello
是添加到每个实例上的方法。
从技术上讲,构造函数是具有以下约定的常规函数:
- 构造函数的名称以大写字母开头,如
Person
、Document
等。 - 构造函数应仅使用
new
运算符调用。
使用构造函数创建对象
使用 new
关键字调用构造函数:
1 2 3 4 5 |
let p1 = new Person("Alice", 25); let p2 = new Person("Bob", 30); p1.sayHello(); // 输出: Hi, I'm Alice p2.sayHello(); // 输出: Hi, I'm Bob |
请注意,ES6 引入了类 class
关键字,允许您定义自定义类型。类只是构造函数的一种语法糖,并带有一些增强功能。
再例如:
下列程序定义一个Person的构造函数:
1 2 3 4 |
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } |
在这个例子中,Person
与普通函数相同,唯一的区别是它的名字以大写字母 P 开头。
要创建 Person
的一个新实例,你需要使用 new
运算符:
1 |
let person = new Person('John','Doe'); |
基本上,new
运算符执行以下操作:
- 创建一个新的空对象,并将其分配给
this
变量。 - 将参数
'John'
和'Doe'
分配给对象的firstName
和lastName
属性。 - 返回
this
值。
它在功能上等同于以下内容:
1 2 3 4 5 6 7 8 9 |
function Person(firstName, lastName) { // this = {}; // add properties to this this.firstName = firstName; this.lastName = lastName; // return this; } |
1 |
因此,以下语句: |
1 2 |
let person = new Person('John','Doe'); |
… 返回的结果与以下语句相同:
1 2 3 4 5 |
let person = { firstName: 'John', lastName: 'Doe' }; |
然而,构造函数 Person
允许您创建多个类似的对象。例如:
1 2 3 |
let person1 = new Person('Jane','Doe'); let person2 = new Person('James','Smith'); |
二、向 JavaScript 构造函数添加方法
对象可能具有操作其数据的方法。要向通过构造函数创建的对象添加方法,您可以使用 this
关键字。例如:
1 2 3 4 5 6 7 8 9 |
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = function () { return this.firstName + " " + this.lastName; }; } |
现在,您可以创建一个新的 Person
对象并调用 getFullName()
方法:
1 2 3 |
let person = new Person("John", "Doe"); console.log(person.getFullName()); |
输出:
1 2 |
John Doe |
构造函数的问题在于,当您创建 Person
的多个实例时,this.getFullName()
在每个实例中都会被复制,这在内存使用上效率不高。
为了解决这个问题,您可以使用原型,以便所有自定义类型的实例共享相同的方法。
三、构造函数的返回值
通常,构造函数隐式返回设置为新创建对象的 this
。但如果它有一个 return
语句,则遵循以下规则:
- 如果
return
返回一个对象,构造函数返回该对象而不是this
。 - 如果
return
返回的不是对象的值,则会被忽略。
四、不使用 new 关键字调用构造函数
从技术上讲,您可以像常规函数一样调用构造函数,而不使用 new
关键字,如下所示:
1 2 |
let person = Person('John','Doe'); |
在这种情况下,Person
只是像常规函数一样执行。因此,Person
函数内部的 this
不绑定到 person
变量,而是绑定到全局对象。
如果你尝试访问 firstName
或 lastName
属性,会收到一个错误:
1 |
<span><code class="hljs language-css"><span class="hljs-selector-tag">console</span><span class="hljs-selector-class">.log</span>(<span class="hljs-selector-tag">person</span><span class="hljs-selector-class">.firstName</span>);</code></span> |
Error:
1 |
<span><code class="hljs language-javascript"><span class="hljs-built_in">TypeError</span>: Cannot read property <span class="hljs-string">'firstName'</span> <span class="hljs-keyword">of</span> <span class="hljs-literal">undefined</span></code></span> |
同样,你也无法访问 getFullName()
方法,因为它绑定到了全局对象上。
1 |
<span><code class="hljs language-css"><span class="hljs-selector-tag">person</span><span class="hljs-selector-class">.getFullName</span>();</code></span> |
Error:
1 |
<span><code class="hljs language-javascript"><span class="hljs-built_in">TypeError</span>: Cannot read property <span class="hljs-string">'getFullName'</span> <span class="hljs-keyword">of</span> <span class="hljs-literal">undefined</span></code></span> |
为了防止构造函数在没有 new
关键字的情况下被调用,ES6 引入了 new.target
属性。
如果使用 new
关键字调用构造函数,new.target
返回对该函数的引用。否则,它返回 undefined
。
以下代码在 Person
函数中添加了一条语句,将 new.target
显示在控制台中:
1 |
1 2 3 4 5 6 7 8 9 10 |
function Person(firstName, lastName) { console.log(new.target); this.firstName = firstName; this.lastName = lastName; this.getFullName = function () { return this.firstName + " " + this.lastName; }; } |
下面的代码返回 undefined
,因为 Person
构造函数是以普通函数的方式调用的:
1 |
let person = Person("John", "Doe"); |
Output:
1 |
<span><code class="hljs language-javascript"><span class="hljs-literal">undefined</span></code></span> |
然而,以下代码由于使用了 new
关键字,因此返回对 Person
函数的引用:
1 2 |
let person = new Person("John", "Doe"); |
输出:
1 2 |
[Function: Person] |
通过使用 new.target
,您可以强制构造函数的调用者使用 new
关键字。否则,您可以抛出错误,如下所示:
1 |
1 2 3 4 5 6 7 8 |
function Person(firstName, lastName) { if (!new.target) { throw Error("Cannot be called without the new keyword"); } this.firstName = firstName; this.lastName = lastName; } |
或者,你可以让语法更灵活一些:如果构造函数的使用者没有使用 new
关键字,你可以在函数内部创建一个新的 Person
对象:
1 2 3 4 5 6 7 8 9 10 11 12 |
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 的库和框架中经常被使用,以使语法更加灵活。
四、构造函数和普通函数的区别
比较项 | 普通函数 | 构造函数 |
---|---|---|
命名规则 | 通常小写 | 通常首字母大写 |
调用方式 | 直接调用 | 使用 new |
this 指向 |
取决于调用者 | 指向新创建的对象 |
返回值 | 根据 return |
默认返回新对象 |
示例对比:
1 2 3 4 5 6 7 8 9 10 11 |
// 普通函数 function greet(name) { this.name = name; } greet("Tom"); // this 指向 window,window.name = "Tom" // 构造函数 function User(name) { this.name = name; } let u = new User("Tom"); // 创建一个 User 对象 |
五、总结
- JavaScript 构造函数是用于创建多个相似对象的常规函数。