主题:通过prototype属性建立基于对象的JavaScript
JavaScript通过一种链接机制来支持继承,而不是通过完全面向对象语言(如Java)所支持的基于类的继承模型。每个JavaScript对象都有一个内置的属性,名为prototype。prototype属性保存着另一个JavaScript对象的引用,这个对象作为当前对象的父对象。
通过点记法引用对象的一个函数或属性时,倘若对象上没有这个函数或属性,只有此时才会使用对象的prototype属性。出现这种情况时,就会检查对象prototype属性所引用的对象,查看是否有所请求的属性或函数。如果prototype属性引用的对象也没有所需的函数或属性,则会进一步检查这个prototype对象(prototype属性引用的对象)本身的prototype属性,依次沿着链向上查找,直到找到所请求的函数或属性,或者到达链尾,如果已经到达链尾还没有找到,则返回undefined。从这个意义上讲,这种继承结构更应是一种“has a”关系,而不是“is a”关系。
如果你习惯于基于类的继承机制,可能要花一些时间来熟悉这种prototype机制。prototype机制是动态的,可以根据需要在运行时配置,而无需重新编译。可以只在需要时才向对象增加属性和函数,而且能动态地把单独的函数合并在一起,来创建动态、全能的对象。对于prototype机制的这种高度动态性,可谓褒贬不一,因为这种机制学习和应用起来很不容易,但是一旦正确地加以应用,这种机制则相当强大而且非常健壮。
这种动态性与基于类的继承机制中的多态概念异曲同工。两个对象可以有相同的属性和函数,但是函数方法(实现)可以完全不同,而且属性可以有完全不同的数据类型。这种多态性使得JavaScript对象能够由其他脚本和函数以统一的方式处理。
下面这个脚本定义了3类对象:Vehicle、SportsCar和CementTruck。Vehicle是基类,另外两个类由此继承。Vehicle定义了两个属性:wheelCount和curbWeightInPounds,分别表示Vehicle的车轮数和总吨位。JavaScript不支持抽象类的概念(抽象类不能实例化,只能由其他类扩展),因此,对于Vehicle基类,wheelCount默认为4,curbWeightInPounds默认为3,000。
SportsCar和CementTruck对象覆盖了Vehicle的refuel和mainTasks函数,因为一般的Vehicle、SportsCar(赛车)和CementTruck(水泥车)会以不同的方式完成这些任务。SportsCar与Vehicle的车轮数相同,所以SportsCar没有必要覆盖Vehicle的wheelCount属性。CementTruck的车轮数和吨位都超过了Vehicle,所以CementTruck中要覆盖wheelCount和curbWeightInPounds属性。
代码清单5-2包含了定义这3个类的JavaScript代码。要特别注意如何在对象定义中对属性和函数附加prototype。还要注意每个对象由一个构造函数定义,构造函数与对象类型同名。
代码清单5-2 inheritanceViaPrototype.js
function Vehicle() { }
Vehicle.prototype.wheelCount = 4;
Vehicle.prototype.curbWeightInPounds = 4000;
Vehicle.prototype.refuel = function() {
return "Refueling Vehicle with regular 87 octane gasoline";
}
Vehicle.prototype.mainTasks = function() {
return "Driving to work, school, and the grocery store";
}
function SportsCar() { }
SportsCar.prototype = new Vehicle();
SportsCar.prototype.curbWeightInPounds = 3000;
SportsCar.prototype.refuel = function() {
return "Refueling SportsCar with premium 94 octane gasoline";
}
SportsCar.prototype.mainTasks = function() {
return "Spirited driving, looking good, driving to the beach";
}
function CementTruck() { }
CementTruck.prototype = new Vehicle();
CementTruck.prototype.wheelCount = 10;
CementTruck.prototype.curbWeightInPounds = 12000;
CementTruck.prototype.refuel = function() {
return "Refueling CementTruck with diesel fuel";
}
CementTruck.prototype.mainTasks = function() {
return "Arrive at construction site, extend boom, deliver cement";
}
代码清单5-3是一个很小的Web页面,展示了这3个对象的继承机制。这个页面只包含3个按钮,每个按钮创建一个类型的对象(Vehicle、SportsCar或CementTruck),并把对象传递到describe函数。describe函数负责显示各个对象的属性值,以及对象函数的返回值。注意,describe方法并不知道它描述的对象是一个Vehicle、SportsCar,还是CementTruck,它只是认为这个对象有适当的属性和函数,并由对象返回自己的值。
代码清单5-3 inheritanceViaPrototype.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JavaScript Inheritance via Prototype</title>
<script type="text/javascript" src="inheritanceViaPrototype.js"></script>
<script type="text/javaScript">
function describe(vehicle) {
var description = "";
description = description + "Number of wheels: " + vehicle.wheelCount;
description = description + "\n\nCurb Weight: " + vehicle.curbWeightInPounds;
description = description + "\n\nRefueling Method: " + vehicle.refuel();
description = description + "\n\nMain Tasks: " + vehicle.mainTasks();
alert(description);
}
function createVehicle() {
var vehicle = new Vehicle();
describe(vehicle);
}
function createSportsCar() {
var sportsCar = new SportsCar();
describe(sportsCar);
}
function createCementTruck() {
var cementTruck = new CementTruck();
describe(cementTruck);
}
</script>
</head>
<body>
<h1>Examples of JavaScript Inheritance via the Prototype Method</h1>
<br/><br/>
<button onclick="createVehicle();">Create an instance of Vehicle</button>
<br/><br/>
<button onclick="createSportsCar();">Create an instance of SportsCar</button>
<br/><br/>
<button onclick="createCementTruck();">Create an instance of CementTruck</button>
</body>
</html>
分别创建三种对象,并用describe函数描述.
通过点记法引用对象的一个函数或属性时,倘若对象上没有这个函数或属性,只有此时才会使用对象的prototype属性。出现这种情况时,就会检查对象prototype属性所引用的对象,查看是否有所请求的属性或函数。如果prototype属性引用的对象也没有所需的函数或属性,则会进一步检查这个prototype对象(prototype属性引用的对象)本身的prototype属性,依次沿着链向上查找,直到找到所请求的函数或属性,或者到达链尾,如果已经到达链尾还没有找到,则返回undefined。从这个意义上讲,这种继承结构更应是一种“has a”关系,而不是“is a”关系。
如果你习惯于基于类的继承机制,可能要花一些时间来熟悉这种prototype机制。prototype机制是动态的,可以根据需要在运行时配置,而无需重新编译。可以只在需要时才向对象增加属性和函数,而且能动态地把单独的函数合并在一起,来创建动态、全能的对象。对于prototype机制的这种高度动态性,可谓褒贬不一,因为这种机制学习和应用起来很不容易,但是一旦正确地加以应用,这种机制则相当强大而且非常健壮。
这种动态性与基于类的继承机制中的多态概念异曲同工。两个对象可以有相同的属性和函数,但是函数方法(实现)可以完全不同,而且属性可以有完全不同的数据类型。这种多态性使得JavaScript对象能够由其他脚本和函数以统一的方式处理。
下面这个脚本定义了3类对象:Vehicle、SportsCar和CementTruck。Vehicle是基类,另外两个类由此继承。Vehicle定义了两个属性:wheelCount和curbWeightInPounds,分别表示Vehicle的车轮数和总吨位。JavaScript不支持抽象类的概念(抽象类不能实例化,只能由其他类扩展),因此,对于Vehicle基类,wheelCount默认为4,curbWeightInPounds默认为3,000。
SportsCar和CementTruck对象覆盖了Vehicle的refuel和mainTasks函数,因为一般的Vehicle、SportsCar(赛车)和CementTruck(水泥车)会以不同的方式完成这些任务。SportsCar与Vehicle的车轮数相同,所以SportsCar没有必要覆盖Vehicle的wheelCount属性。CementTruck的车轮数和吨位都超过了Vehicle,所以CementTruck中要覆盖wheelCount和curbWeightInPounds属性。
代码清单5-2包含了定义这3个类的JavaScript代码。要特别注意如何在对象定义中对属性和函数附加prototype。还要注意每个对象由一个构造函数定义,构造函数与对象类型同名。
代码清单5-2 inheritanceViaPrototype.js
function Vehicle() { }
Vehicle.prototype.wheelCount = 4;
Vehicle.prototype.curbWeightInPounds = 4000;
Vehicle.prototype.refuel = function() {
return "Refueling Vehicle with regular 87 octane gasoline";
}
Vehicle.prototype.mainTasks = function() {
return "Driving to work, school, and the grocery store";
}
function SportsCar() { }
SportsCar.prototype = new Vehicle();
SportsCar.prototype.curbWeightInPounds = 3000;
SportsCar.prototype.refuel = function() {
return "Refueling SportsCar with premium 94 octane gasoline";
}
SportsCar.prototype.mainTasks = function() {
return "Spirited driving, looking good, driving to the beach";
}
function CementTruck() { }
CementTruck.prototype = new Vehicle();
CementTruck.prototype.wheelCount = 10;
CementTruck.prototype.curbWeightInPounds = 12000;
CementTruck.prototype.refuel = function() {
return "Refueling CementTruck with diesel fuel";
}
CementTruck.prototype.mainTasks = function() {
return "Arrive at construction site, extend boom, deliver cement";
}
代码清单5-3是一个很小的Web页面,展示了这3个对象的继承机制。这个页面只包含3个按钮,每个按钮创建一个类型的对象(Vehicle、SportsCar或CementTruck),并把对象传递到describe函数。describe函数负责显示各个对象的属性值,以及对象函数的返回值。注意,describe方法并不知道它描述的对象是一个Vehicle、SportsCar,还是CementTruck,它只是认为这个对象有适当的属性和函数,并由对象返回自己的值。
代码清单5-3 inheritanceViaPrototype.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JavaScript Inheritance via Prototype</title>
<script type="text/javascript" src="inheritanceViaPrototype.js"></script>
<script type="text/javaScript">
function describe(vehicle) {
var description = "";
description = description + "Number of wheels: " + vehicle.wheelCount;
description = description + "\n\nCurb Weight: " + vehicle.curbWeightInPounds;
description = description + "\n\nRefueling Method: " + vehicle.refuel();
description = description + "\n\nMain Tasks: " + vehicle.mainTasks();
alert(description);
}
function createVehicle() {
var vehicle = new Vehicle();
describe(vehicle);
}
function createSportsCar() {
var sportsCar = new SportsCar();
describe(sportsCar);
}
function createCementTruck() {
var cementTruck = new CementTruck();
describe(cementTruck);
}
</script>
</head>
<body>
<h1>Examples of JavaScript Inheritance via the Prototype Method</h1>
<br/><br/>
<button onclick="createVehicle();">Create an instance of Vehicle</button>
<br/><br/>
<button onclick="createSportsCar();">Create an instance of SportsCar</button>
<br/><br/>
<button onclick="createCementTruck();">Create an instance of CementTruck</button>
</body>
</html>
分别创建三种对象,并用describe函数描述.