当前位置: 代码迷 >> JavaScript >> JavaScript定义“种”的几种方式
  详细解决方案

JavaScript定义“种”的几种方式

热度:99   发布时间:2012-10-27 10:42:25.0
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中没有类。

new是个特殊的操作符,用来创建一个新对象。function是一种特殊对象,可以被new操作。new function X的时候,将创建一个新对象,同时把X function的上下文设置为这个新的对象,接着运行X function中的每一行代码。

function这个特殊对象有一个特殊的属性叫做prototype,这个属性估计是为new而生的,每次执行new操作,prototype属性的每个属性将被复制到新生成的对象上。

JavaScript如此简单,没必要给它扣上“类”、“继承”之类的名字。
3 楼 yuan 2009-04-27  
那instanceof是根据什么判断的?constructor?还是每个对象都有一个prototype的引用?
4 楼 yuan 2009-04-27  
看来我还是比较聪明的。搜索到一篇笔记:关于JavaScript的prototype和instanceof

引用
同时,每个对象都会有一个内部的属性_proto_(不同的javascript虚拟机实现用的名字可能不同),这个属性对js开发人员不可见,只在虚拟机内部使用。每当创建一个对象的时候,这个对象的_proto_就会被赋值为这个对象的构造函数的prototype,这样对象的_proto_属性和构造函数的prototype引用相同的对象,并且一旦对象创建完成,_proto_属性就不会改变。这样通过对象的_proto_属性,以及_proto_所引用的对象的_proto_属性,就构成了一个_proto_链。当访问一个对象的属性和方法的时候,js虚拟机正是通过这个_proto_链来查找的。

关于instanceof:
     假设有一条这样的语句:
     o instanceof c;
     在上面的语句执行过程中,虚拟机会把c.prototype和o的_proto_链上的节点逐个进行比较,如果找到相等的节点,则返回true,否则返回false。
5 楼 reyesyang 2012-05-28  
上面的四位同学讨论的好激烈!
  相关解决方案