日期:2014-05-16  浏览次数:20341 次

JavaScript定义“类”的几种方式
之所以给“类”加上引号,是因为书上说JavaScript中其实没有类这么个概念。
注:文中代码及一些文字摘自《JavaScript高级程序设计》,有些地方稍有修改。

1、工厂方式:
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
}

书上说,这种方式有人反对,原因有2:首先是语义上的原因,它看起来不像使用带有构造函数的new运算符那么正规。第二是功能上的原因,以上代码每创建一个Car时,都要创建一个新的function:showColor。
var car1 = createCar("red", 2, 23)
var car2 = createCar("blue", 4, 30)
alert(car1.showColor==car2.showColor) //显示“false”,表示这2个function不是同一个实例。


第二个问题有个解决办法,就是把showColor这个function定义在工厂函数createCar的外面,然后在createCar里面给oTempCar添加一个showColor属性,指向外面的showColor函数。但这种方式不太好,理由是:该函数看起来不像对象的方法。

2、构造函数方式:
function Car(sColor, iDoors, iMpg){
	this.color = sColor
	this.doors = iDoors
	this.mpg = iMpg
	this.showColor = function(){
		alert(this.color)
	}
}

这种方式通过new运算符调用Car这个构造函数,构造之前会先隐式的创建一个对象,只有用this才能访问这个对象,然后可以直接给this添加属性并赋值,构造函数默认返回这个被新建出来的对象,不用显式使用return。

但以上方式和工厂方式存在同样的一个问题:每创建一个Car时,都要创建一个新的function:showColor。当然也可以用上面提到的方法解决。

3、原型方式
function Car(){
}

Car.prototype.color = "red"
Car.prototype.doors = 4
Car.prototype.mpg = 23
Car.prototype.showColor = function(){
	alert(this.color)
}

prototype就是所谓的原型,可以看作是一个模板吧,对象的模板,跟“类”的概念差不多。这里解决了前面2种方式的问题,但新的问题随之而来:首先这种方式不能给构造函数传递参数,必须给属性一个一个的赋值,否则得到的对象都是一样的。还有个更糟糕的问题是,假如某个属性指向一个对象,那么这个属性将被所有对象共享:
function Car(){
}

Car.prototype.color = "red"
Car.prototype.doors = 4
Car.prototype.mpg = 23
Car.prototype.drivers = new Array("Mike", "Sue");
Car.prototype.showColor = function(){
	alert(this.color)
}

var car1 = new Car
var car2 = new Car
car1.drivers.push("Matt")
alert(car2.drivers)

如上,给car1添加了一个driver,car2中也多出了这么个driver,这是个大问题。
原因在于:给prototype添加的属性是一个对象,构造Car时便会把这个对象的引用复制给每个car。而之前的构造函数方式不存在这种问题,是因为构造函数是在每次创建对象时单独给每个添加属性,而不是一开始在prototyp这样的“对象模板”中写死了。

4、混合方式(构造函数&原型)
很明显,解决以上所有问题的方法产生了:把构造函数方式和原型方式混合,取长补短。
既然方法必须共享,那么就采用原型方式定义方法;既然属性不能共享,那么就采用构造函数方式定义属性。Perfect!
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)
}

不过仍有些开发者觉得这种方法不够perfect。

5、动态原型方式
有开发者认为,在构造函数内部找属性,在外部找方法的做法不合逻辑,因此他们设计出了动态原型方式,这种方式把对象的方法给定义到了构造函数内部,但是是在内部使用prototype定义方法。由于构造函数本身在每构造一个对象时都会执行一次,所以对象的方法会在每构造一次时重新创建,覆盖掉之前的方法。为了防止每次都给prototype创建新的function,可以给Car这个对象(Car本身也是个对象,一个function对象)添加一个标记,用于判断prototype是否被初始化过:
function Car(sColor, iDoors, iMpg){
	this.color = sColor
	this.doors = iDoors
	this.mpg = iMpg
	this.drivers = new Array("Mike", "Sue")
	
	if(!Car._initialized){
		Car.prototype.showColor = function(){
			alert(this.color)
		}
		Car._initialized = true
	}
}


还有第6种方式,不过第6种方式存在着和工厂方式、构造函数方式相同的问题,而且还有第15章才能明白的原因,所以这里不做笔记了,15章再说。

OK,回头看一下,其实我觉得混合方式挺好的,动态原型写起来反倒有些麻烦。嗯,看来我跟大多数人想法一样,书上说使用最广泛的是混合构造函数/原型方式。

这篇笔记是我JavaScript的OOP入门篇。
1 楼 yuan 2009-04-20  
回头想想有点奇怪ho..都说JavaScript不存在类的概念,那为什么有个instanceof关键字呢?
2 楼 yuan 2009-04-27  
一点感悟:

JavaScript中没