本文深入解析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