本文详细解析JavaScript中定义类的七种方式,包括工厂模式、构造函数模式、原型模式、混合构造函数/原型模式、动态原型模式以及混合工厂模式。通过对比各模式的优缺点,如内存占用、方法共享及实例属性隔离等问题,帮助开发者理解不同实现机制,选择最适合项目需求的类定义方案。
关于JavaScript中类的继承机制,可参考阮一峰的博文《Javascript继承机制的设计思想》,其论述十分透彻。
JavaScript实例化遇到的问题
一、JavaScript中实例化对象时遇到的问题:
以下使用《javascript高级程序设计》中的示例进行说明。假设现在定义了一个名为car的对象,它是Object类的实例,代码如下:
代码如下:
var oCar=new Object();
oCar.color = "red";
oCar.doors = 4;
oCar.mpg = 23;
oCar.showColor = function () {
alert(this.color);
}; 现在如果需要创建另一个类似的实例,你可能会采用如下定义方式:
代码如下:
var oCar2 = new Object();
oCar2.color = "blue";
oCar2.doors = 5;
oCar2.mpg = 25;
oCar2.showColor = function () {
alert(this.color);
}; 这种方式存在的问题是,每个对象都需要重新定义其字段和方法,操作繁琐。
工厂方式实现类
二、类的定义--工厂方式实现:
对上述示例进行封装,利用函数的返回值来创建对象:
代码如下:
function createCar() {
var oTempCar = new Object();
oTempCar.color = "red";
oTempCar.doors = 4;
oTempCar.mpg = 23;
oTempCar.showColor = function () {
alert(this.color);
};
return oTempCar;
} 调用方式如下:
var oCar1 = createCar();
var oCar2 = createCar();
这种方式被称为工厂方式,看起来确实简化了代码,创建对象时不再需要编写大量重复代码。由于每个属性(color, doors, mpg)的值都是固定的,为了支持不同参数,可以利用参数传递来实现:
代码如下:
function createCar(sColor, iDoors, iMpg) {
var oTempCar = new Object();
oTempCar.color = sColor;
oTempCar.doors = iDoors;
oTempCar.mpg = iMpg;
oTempCar.showColor = function () {
alert(this.color);
};
return oTempCar;
}
var oCar1 = createCar("red", 4, 23);
var oCar2 = createCar("red", 4, 23);
oCar1.showColor();
oCar2.showColor(); 这种方式虽然实现了对象创建,且调用简便,但存在两个主要缺陷:
1、从语义上看,创建对象时未使用new运算符,显得不够规范(通常创建对象会使用new运算符)。
2、不符合面向对象的封装特性。在此例中,oCar1和oCar2各自拥有独立的showColor方法实现,但实际上它们共享的是同一个函数逻辑。
为解决共享函数的问题,可以利用函数指针。在createCar函数外部再创建一个showColor函数,并让oTempCar的showColor方法指向该外部函数:
代码如下:
function showColor() {
alert(this.color);
}
function createCar(sColor, iDoors, iMpg) {
var oTempCar = new Object();
oTempCar.color = sColor;
oTempCar.doors = iDoors;
oTempCar.mpg = iMpg;
oTempCar.showColor = showColor;
return oTempCar;
}
var oCar1 = createCar("red", 4, 23);
var oCar2 = createCar("red", 4, 23);
oCar1.showColor();
oCar2.showColor(); 虽然这样解决了重复创建函数的问题,但使得showColor函数看起来不像对象的方法。
构造函数方式实现类
三、类的定义--构造函数方式实现:
代码如下:
function Car(sColor, iDoors, iMpg) {
//通过构造函数的形式,会为每个对象生成独立的属性和函数
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.showColor = function () {
alert(this.color);
};
}
var oCar1 = new Car("red", 4, 23);
var oCar2 = new Car("red", 4, 23);
oCar1.showColor();
oCar2.showColor(); 在Car类中,this指针代表Car的一个实例,因此无需返回值。虽然构造函数方式实现了类的定义,但与工厂方式类似,它也为每个实例创建了单独的方法。尽管可以像工厂函数那样在外部创建函数并利用指针解决此问题,但从语义上讲并无意义。
原型方式实现类
四、类的定义--原型方式实现:
利用对象的prototype属性,将其视为创建新对象所依赖的原型。使用空构造函数来设置类名,然后将所有属性和方法直接赋予prototype属性。
代码如下:
function Car() {
}
Car.prototype.color = "red";
Car.prototype.doors = 4;
Car.prototype.mpg = 23;
Car.prototype.showColor = function () {
alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
alert(oCar1 instanceof Car);//output true这里存在两个问题: 1、构造函数没有参数。使用原型时,无法通过给函数参数传递参数来初始化属性值。
2、在有多个实例时,对其中一个实例的属性的更改会影响到另外一个实例的属性。
测试代码:
代码如下:
var oCar1 = new Car();
oCar1.color = "Green";
var oCar2 = new Car();
oCar2.color = "Black";
alert(oCar1.color); //output Green
alert(oCar2.color); //output Black
alert(oCar1.color); //output Black 当然,解决这个问题也有办法,即采用混合的构造函数/原型方式。
混合构造函数/原型方式
五、类的实现--混合的构造函数/原型方式实现
这种实现方式将每个类的实例中共享的属性或方法放入原型链中实现,而不需要共享实现的属性和方法则放在构造函数中。这是目前使用最广泛的类实现方式。
代码如下:
function Car(sColor, iDoors, iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.drivers = new Array("Mike", "Sue");
}
Car.prototype.showColor = function () {
alert(this.color);
};
var oCar1 = new Car("red", 4, 23);
var oCar2 = new Car("blue", 3, 24);
oCar1.drivers.push("Matt");
alert(oCar1.drivers);
alert(oCar2.drivers);六、类的定义--动态原型方式实现 动态原型方式实现类
与混合的构造函数/原型方式相比,这种方式提供了一种更友好的编程风格(在混合构造函数/原型方式中,showColor方法的定义是在方法体外实现的,而不是在构造函数的方法体内完成的)。这种类的定义方式使用也非常广泛。
代码如下:
function Car(sColor, iDoors, iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.divers = new Array("Mike", "Sue");
if (typeof Car._initialized == "undefined") {
Car.prototype.showColor = function () {
alert(this.color);
};
Car._initialized = true;
} 混合工厂方式实现类
七、类的定义--混合工厂方式实现
代码如下:
function Car() {
var oTempCar = new Object();
oTempCar.color = "red";
oTempCar.doors = 4;
oTempCar.mpg = 23;
oTempCar.showColor = function () {
alert(this.color);
};
return oTempCar;
}
var car = new Car();
car.showColor(); 这种方式与工厂方式看起来相似。由于在Car()构造函数内部调用了new运算符,因此会忽略掉位于构造函数之外的new运算符。在构造函数内部创建的对象被传回变量var。虽然看起来有了new运算符,比工厂方式有所进步,但这种实现方式同样会出现重复创建方法的问题,因此不推荐使用这种方式来定义类。