深入理解JavaScript构造函数模式:从基础到原型与调用陷阱

作者:半夏

时间:2026-10-28

浏览:587

本文深入解析JavaScript中的构造函数模式。通过实例演示如何定义和使用构造函数创建对象,探讨利用prototype属性共享方法以优化内存的策略。同时详细剖析不使用new关键字调用构造函数时的this指向问题,提供强制使用new的解决方案,并对比原始包装函数与原始类型的区别,帮助开发者掌握核心概念。

文章目录

构造函数模式基础

介绍

构造函数是JavaScript中用于创建特定类型对象的重要机制。对于初学者而言,理解构造函数的基本概念至关重要。构造函数不仅定义了对象的类型,还允许在首次创建对象时通过参数设置对象的成员属性值。开发者可以自定义构造函数,在其中声明自定义类型对象的属性和方法。

基本用法

在JavaScript中,构造函数通常用于实现实例化过程。虽然JavaScript没有传统的类概念,但提供了特殊的构造函数机制。通过new关键字调用自定义的构造函数,可以指示JavaScript创建一个新对象,并将构造函数内部定义的所有成员分配给该新对象。在构造函数执行期间,this关键字指向新创建的对象实例。基本用法示例如下:

代码如下:

function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
this.output= function () {
return this.model + "走了" + this.miles + "公里";
    };
}
var tom= new Car("大叔", 2009, 20000);
var dudu= new Car("Dudu", 2010, 5000);
console.log(tom.output());
console.log(dudu.output());

上述示例展示了最简单的构造函数模式,但存在两个主要问题:首先,这种模式下的继承实现较为复杂;其次,output()方法在每次创建对象实例时都会被重新定义。为了优化内存使用,特别是当需要创建大量Car类型实例时,最佳实践是让所有Car实例共享同一个output()方法。

为解决上述问题,可以采用以下改进方式:

代码如下:

function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
this.output= formatCar;
}
function formatCar() {
 return this.model + "走了" + this.miles + "公里";
}

虽然上述方式可行,但还有更优的解决方案。

原型与内存优化

构造函数与原型

JavaScript中的函数具有一个名为prototype的原型属性。当通过构造函数创建对象时,该构造函数原型上的所有属性都会在新创建的对象上可用。基于这一机制,多个Car对象实例可以共享同一个原型对象。我们在此基础上扩展之前的代码示例:

代码如下:

function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
}
/*
注意:这里我们使用了Object.prototype.方法名,而不是Object.prototype
主要是用来避免重写定义原型prototype对象
*/
Car.prototype.output= function () {
 return this.model + "走了" + this.miles + "公里";
};
var tom = new Car("大叔", 2009, 20000);
var dudu = new Car("Dudu", 2010, 5000);
console.log(tom.output());
console.log(dudu.output());

在此模式下,output()方法作为单一实例被所有Car对象实例共享使用,从而节省了内存空间。

另外:我们推荐构造函数以大写字母开头,以便区分普通的函数。

构造函数调用方式与new关键字

只能用new吗?

在前面的示例中,Car函数始终通过new关键字创建对象。是否存在其他调用方式?实际上还有两种替代方法,列举如下:

代码如下:

function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
// 自定义一个output输出内容
this.output = function () {
return this.model + "走了" + this.miles + "公里";
    }
}
//方法1:作为函数调用
Car("大叔", 2009, 20000); //添加到window对象上
console.log(window.output());
//方法2:在另外一个对象的作用域内调用
var o = new Object();
Car.call(o, "Dudu", 2010, 5000);
console.log(o.output());

上述代码中的方法1较为特殊:如果不使用new关键字直接调用函数,this将指向全局对象window。我们可以通过以下代码进行验证:


代码如下:

//作为函数调用
var tom = Car("大叔", 2009, 20000);
console.log(typeof tom); // "undefined"
console.log(window.output()); // "大叔走了20000公里"

此时,变量tom的值为undefined,而window.output()能正确输出结果。如果使用new关键字,则不会出现此问题,验证如下:


代码如下:

//使用new 关键字
var tom = new Car("大叔", 2009, 20000);
console.log(typeof tom); // "object"
console.log(tom.output()); // "大叔走了20000公里"

强制使用new关键字的实现

强制使用new

前面的示例展示了不使用new关键字可能带来的问题。那么,是否有办法强制构造函数必须使用new关键字?答案是肯定的,具体实现代码如下:

代码如下:

function Car(model, year, miles) {
if (!(this instanceof Car)) {
return new Car(model, year, miles);
    }
this.model = model;
this.year = year;
this.miles = miles;
this.output = function () {
return this.model + "走了" + this.miles + "公里";
    }
}
var tom = new Car("大叔", 2009, 20000);
var dudu = Car("Dudu", 2010, 5000);
console.log(typeof tom); // "object"
console.log(tom.output()); // "大叔走了20000公里"
console.log(typeof dudu); // "object"
console.log(dudu.output()); // "Dudu走了5000公里"

该实现通过判断this是否为Car的实例来决定行为:如果使用了new关键字,(this instanceof Car)为真,将继续执行后续的参数赋值;如果未使用new,(this instanceof Car)为假,则会重新new一个实例并返回。

原始包装函数与原始类型

原始包装函数

JavaScript中有三种原始包装函数:Number、String和Boolean,有时也会同时使用它们:

代码如下:

// 使用原始包装函数
var s = new String("my string");
var n = new Number(101);
var b = new Boolean(true);
// 推荐这种
var s = "my string";
var n = 101;
var b = true;

推荐仅在需要保留数值状态时使用这些包装函数。关于原始类型与包装对象的区别,可以参考以下代码示例:


代码如下:

// 原始string
var greet = "Hello there";
// 使用split()方法分割
greet.split(' ')[0]; // "Hello"
// 给原始类型添加新属性不会报错
greet.smile = true;
// 单没法获取这个值(18章ECMAScript实现里我们讲了为什么)
console.log(typeof greet.smile); // "undefined"
// 原始string
var greet = new String("Hello there");
// 使用split()方法分割
greet.split(' ')[0]; // "Hello"
// 给包装函数类型添加新属性不会报错
greet.smile = true;
// 可以正常访问新属性
console.log(typeof greet.smile); // "boolean"

总结

本章主要讲解了构造函数模式的使用方法、调用方法以及new关键字的区别,希望大家在使用的时候有所注意。

参考:http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#constructorpatternjavascript

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章