本文深入解析JavaScript中构造函数与普通函数的核心区别,包括命名规范、this指向及new操作符的作用。详细阐述new关键字执行的四步流程,并针对构造函数导致的方法重复创建与内存浪费问题,探讨从全局函数封装到原型对象优化的演进过程,帮助开发者理解对象创建机制并提升代码性能。
首先需要明确一个核心概念:构造函数本质上仍然是函数。
在开发中,我们常利用构造函数来创建实例对象,例如通过调用Object()和Array()构造函数来实例化对象和数组。
尽管语法定义上构造函数与普通函数完全一致,但它们在行为上存在三个主要差异。
第一,命名规范不同。按照惯例,构造函数的首字母必须大写,而普通函数首字母小写。这一约定源自面向对象编程传统,旨在ECMAScript中帮助开发者区分两者。
第二,内部this指向与返回值不同。构造函数内部使用this关键字指向即将生成的实例,且无需显式使用return语句,默认返回this。例如Person()函数虽未显式返回,但person1变量仍获得了包含name和age属性的Person实例。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//1.定义构造函数
function Person(name,age){
this.age = age;
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
// 2.生成实例对象
var person1 = new Person('小苏','18');
// 3.打印实例对象
console.log(person1);
</script>
</body>
</html>
运行结果如下所示。

第三,调用方式不同。构造函数必须配合new操作符使用。任何函数只要通过new调用即视为构造函数,否则仅为普通函数。
1、构造函数的定义与调用
构造函数,亦称自定义构造函数,用于以函数形式为自定义对象类型定义属性和方法。
具体示例如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//1.定义构造函数
function Person(name,age){
this.age = age;
this.name = name;
this.sayName = function(){
console.log(this.name);
}
}
// 2.生成实例对象
var person1 = new Person('小苏','18');
// 3.调用方法
person1.sayName();
</script>
</body>
</html>
2、new关键字的用途
当使用new关键字执行构造函数时,会依次执行以下四个步骤:
- 第一步,通过new操作符在内存中开辟新地址,创建实例对象。
- 第二步,确定构造函数内部this的指向,使其指向新创建的实例本身。
- 第三步,执行构造函数体内的代码,为新实例添加属性。
- 第四步,隐式返回该新创建的对象。
以前文生成person1实例的代码为例进行解析。
第一步:在内存中为person1实例分配新地址。
第二步:将person1实例的this指向绑定至person对象本身。
第三步:为person1实例添加name、age属性,以及sayName方法(其值为一个函数)。
第四步:返回该person1实例。
具体实例演示。
var person1 = new Person("Bob", 18);
这行代码的逻辑等价于以下手动实现new功能的代码。
// 演示 new 的功能
function Person(name,age) {
// 1.创建一个新对象
var instance = new Object();
// 2.将函数内部的 this 指向了这个新对象
this = instance;
// 3.执行构造函数内部的代码
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
};
// 4.将新对象作为返回值
return instance;
}
这意味着将instance赋值给person1,即执行person1 = instance。其实际执行过程如下。
// 演示 new 的功能
function Person(name,age) {
// 1.创建一个新对象
var person1 = new Object();
// 2.将函数内部的 this 指向了这个新对象
this = person1;
// 3.执行构造函数内部的代码
this.name = name;
this.age = age;
this.sayName = function () {
console.log(this.name);
};
// 4.将新对象作为返回值
return person1;
}
3、构造函数的问题:内存浪费
基本事实是:不同实例中的sayName属性并非同一个函数。例如。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 1.自定义构造函数
function Person(name,age) {
this.name = name;
this.age = age;
// this 内部的 type 属性值是不变的
this.type = "human";
// 每个对象的 sayName 方法也是一样的
this.sayName = function () {
console.log(this.name);
};
}
var person1 = new Person('Bob',18);
var person2 = new Person('Mike',20);
// 2,判断各自的方法是否是同一个函数
console.log(person1.sayName === person2.sayName); // false
</script>
</body>
</html>
解释:通过console.log(person1.sayName === person2.sayName)判断可知,两个实例的方法并不相等。这意味着每个实例都拥有独立的函数副本,占用多个内存空间,造成资源浪费。
解决函数占用内存的方法是提取对象内部函数,将其作为公共函数处理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 解决方法1: 将公共的函数提取到构造函数之外
function sayName() {
console.log(this.name);
}
function sayAge() {
console.log(this.age);
}
//1.自定义对象
function Person(name,age) {
this.name = name;
this.age = age;
// this 内部的 type 属性值是不变的
this.type = "human";
// 每个对象的 sayName 方法也是一样的
this.sayName = sayName;
this.sayAge = sayAge;
}
//2.实例化对象
var person1 = new Person('Bob',18);
var person2 = new Person('Mike',20);
console.log(person1.sayName === person2.sayName); // true
</script>
</body>
</html>
然而,若存在多个公共函数,需在外部创建多个函数,这可能导致命名冲突。
解决方案是将多个公共函数封装到一个对象中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 3.将多个公共的函数封装到一个对象
var fns = {
sayName : function () {
console.log(this.name);
},
sayAge : function () {
console.log(this.age);
}
};
// 1.自定义对象
function Person(name,age) {
this.name = name;
this.age = age;
this.type = "human";
this.sayName = fns.sayName;
this.sayAge = fns.sayAge;
}
// 2.生成对象实例
var person1 = new Person("Bob",18);
var person2 = new Person("Mike",20);
// person1.sayName();
console.log(person1.sayName === person2.sayName);
console.log(person1.sayAge === person2.sayAge);
</script>
</body>
</html>
解释:将多个函数封装至单一对象,可有效解决函数名冲突问题。
通过设置全局可访问的函数,所有实例均可共享访问,无需重复创建。
但此方案存在缺陷:若将所有函数处理为全局函数,无法实现自定义类型对象的属性封装。因为属性需定义在函数外部作为全局变量才能调用,这反而污染了全局变量空间,故非理想方案。
为此,引入原型概念。利用原型机制可完美解决上述问题,实现良好的封装与共享。
总结
本文到此结束,希望以上内容对您有所帮助,欢迎持续关注本站获取更多技术干货。