原型(Prototype):减少AJAX的开发痛苦
作者:
Bruce Perry
April 05, 2006
本文所描述的原型(Prototype),是一个开源的JavaScript库,用来为AJAX应用创建一个对象。我通过描述一个面向Web应用的环境系统来解释如何使用原型(Prototype),这个系统用来显示年度的二氧化碳浓度水平。首先,我将讨论原型(Prototype)的好处,并且描述怎么在你的应用里安装原型(Prototype);其次,我还将深入地探讨这个系统怎么使得原型(Prototype)库有着很好的应用实践。
什么是原型(Prototype
)
为什么我不为我的应用直接创建一个简单的、成熟的JavaScript对象(POJO),而是引进一个开源库?第一,原型(Prototype)收集了很多漂亮的有关于JavaScript的捷径用法,它减少输入,并且避免重复发明轮子。最常用的、值得夸奖的捷径方法是
$("mydiv")
,这是一个原型(Prototype)函数,返回一个与id为“mydiv”的HTML标签相关的文档对象模型(DOM)元素。这种类型的简化完全值得我们在安装原型(Prototype)上的花销。而不安装它、与
$("mydiv")
相等的表达式为:
document.getElementById("mydiv");
另外一个有用的原型(Prototype)的捷径方法是
$F("mySelect")
,用来返回一个Web页面上的HTML表单的元素的值,例如一个选择列表。一旦你习惯了原型(Prototype)的严谨的、透明的语法,你就会随时使用这些捷径方法。原型(Prototype)也包含了很多的定制对象、方法和很多用来编译JavaScript对象的扩展。例如
Enumeration
和
Hash
对象(我在下面将要讨论到)
最后,原型(Prototype)也通过它自己的
Ajax.Request
和相关的对象包装了
XMLHttpRequest
的功能。所以你不用费心去编写代码为各种各样的浏览器去启动对象。
安装
怎样安装原型(Prototype)呢?首先,在
prototype.conio.net网站上下载它,原型(Prototype)是一个开源的项目,能够在
an MIT-style license授权下使用。下载的文件包括
prototype.js文件,这是一个JavaScript文件,它定义了各样各样的在你的应用中要使用到的方法、对象。在你的应用中添加
prototype.js文件的方法是在HTML文件中使用一个
script
标签。
<head>...
<script src="js/prototype.js" type="text/javascript">
</script>
<script src="js/co2.js" type="text/javascript">
</script>
<script src="js/eevpapp.js" type="text/javascript">
</script>...
</head>
在你的应用程序中的包含HTML文件的同一极目录里,也包含了一个叫
js的目录。这个
js目录包含了
prototype.js,在
co2.js文件中定义了一个对象,这将在本文中加以描述,就跟客户端的其他代码在另外一个文件:
eevapp.js中一样。
既然应用已经引入了原型(Prototype)文件,那么其他的引入了
.js文件的JavaScript代码就能够使用原型对象及其扩展,如果它们在本地被声明和定义了的话。JavaScript不需要像例如
import
和
require
这样的语句来使用其他JavaScript文件的对象,这些JavaScript文件只需要浏览器一般引入就行了。
气候变化
原型(Prototype)是怎么来帮助我们实现应用的需求的呢?需求又是什么呢?
用户接口是一个Web浏览器,例如Safari 1.3、Firefox 1.5、Opera 8.5或者 Internet Explorer 6。应用被设计来显示小数据量的年度的大气层二氧化碳水平。这是数值性的数据,像377,代表的是某一个年度在大气中的百万分之几的二氧化碳的水平。我决定在一个JavaScript对象中存储这些年份和数值,这些对象能够从应用中下载到浏览器上。没有必要使用数据库来存储这些数据,因为它们规模太小,不需要安全或者认证。这些数据被设计来供感兴趣的公众使用。然而,应用的确有了如果必要的话,在飞行中对JavaScript对象添加新的数据的能力。
CO2:Applet
图1-1显示了应用的浏览器界面。左上角是一个Applet,用来显示大气中二氧化碳的水平,当用户选择一个年份的时候。
数据是从运行在Hawaii的政府网站
Mauna Loa Observatory上获得。它的测量是在高海拔上的一般测量。全世界的科学家都在它们的气候变迁研究中使用这些数据。
有46个年度等级,从1959-2004(2005年的数据现在还没有获得)。因而,这使得我们使用JavaScript对象来保存信息。甚至当我们需要使用这个工具选择显示这些年的月度水平(有超过500的分离数据),我们也是在客户端保存数据,没有数据库或额外的服务器。
原型(Prototype
)在哪里被使用
eevapp.js文件包含了当用户在选择框选择一个年度的时候需要执行的代码:
//instantiate an object
//defined in the co2.js file
var co2lev = new CO2Levels();
//the onload event handler executes
//when the browser is finished loading the page.
window.onload=function(){
$("co2_select").onchange=function(){
$("co2ppm").innerHTML=
co2lev.getYear($F("co2_select"));
}
};
值
"co2_select"
是用户选择一个年份时选择框的id值。
<select id="co2_select" .../>
代码:
$("co2_select")
返回一个DOM元素的引用。和表达式
document.getElementById("co2_select");
相同。
代码给选择框的
onchange
事件处理属性赋予了一个JavaScript方法,如:
$("co2_select").onchange=function(){...}
.
用一个简单的解释,当用户在
select
标签里选择一个年份的时候,浏览器执行指定的方法。这个方法要做什么呢?这个方法取得一个HTML
div
元素的
id
"co2ppm",再一次使用原型(Prototype)的快捷方法,这个
div
元素的
innerHTML
属性,代表的是用户在浏览器中看到的内容,被赋值为一个方法调用的返回值:
co2lev.getYear($F("co2_select"));
在
eevapp.js文件的最开始,
co2lev
对象被初始化。
var co2lev = new CO2Levels();
这个对象有一个
getYear()
方法。这个方法取得一个字符串,代表年度的参数,如
"1999"
,返回一个相关年份的二氧化碳的水平。
精炼的语法
那个美元符号在表达式
$F("co2_select")
中又出现了一次,原型(Prototype)方法
$F()
返回的是HTML表单元素的值,在这个例子中,是选择框,我把它的
id
作为一个参数传递到方法中。当用户选择一个不同的年份的时候,应用使用这个方法来改变显示CO2的水平。
一个小提示:记住,如果选择框的子
option
没有
value
属性,
$F()
不能从一个选择框中返回值。就像:
<option value="1959">1959</option>
。换句话说,至少在我使用的原型(Prototype)版本1.4.0中,如果我省掉了
value="..."
,
$F()
不会有返回值。
对象视图
CO2Levels
对象的定义出现在一个不同的JavaScript文件中,即
co2.js。图1-2显示了一个描述该对象的UML类图。
图1-2 UML类图
下面是
CO2Levels
对象的整个代码。第一行初始化一个新的对象,使用从原型(Prototype)继承来的语法。局部变量
levels
是一个对象,像一个联合数组,和年度以及对应的CO2水平相关联。我因为可读性省略了大多数的年份。
var CO2Levels=Class.create();
CO2Levels.prototype = {
/*
Source: http://cdiac.esd.ornl.gov/ftp/trends/co2/maunaloa.co2
Mauna Loa Observatory, Hawaii
*/
initialize: function(){
this.levels={ "1959":315.98,"1960":316.91,
"1961":317.65,"1962":318.45,
"2003":375.64,"2004":377.38};
this.levelsHash=$H(this.levels);
},
getYear: function(year){
if (! isNaN(year)) {
return this.levelsHash[year];
} else {
return 377;
}
},
keys: function(){
return this.levelsHash.keys();
},
values: function(){
return this.levelsHash.values();
},
inspect: function(){
alert( this.levelsHash.inspect());
},
add: function(year,level){
var tmp = new Object();
tmp[year] = level;
this.levelsHash=this.levelsHash.merge(tmp);
}
}
创建原型(Prototype
)对象
在Prototype中使用
Class.create()
方法返回一个JavaScript对象,这个对象通过
initialize()
方法提供这个对象的一个新的实例。这和构造器方法的功能相同,例如Java中的。当每次代码产生一个新的
CO2Levels
对象,
initialize()
方法都要被调用。代码的余下部分为这个
CO2Levels
对像定义原型(Prototype),或者蓝图。包括它的
initialize()
方法的各种行为。
initialize()
方法要做什么呢?它创建了一个叫
levels
的局部变量,这个局部变量引用了一个保存所有数据的对象,这些数据包括CO2水平以及相应的年度。代码接着把这些对象转化到
Hash
对象中,为对象提供更多的功能(例如察看对象内容和新增新的数据的功能)。
this.levelsHash=$H(this.levels);
原型Hash
对象
这里又有一个语法
$H()
,这个方法获取一个JavaScript对象作为参数,返回原型(Prototype)的
Hash
对象。和其他语言的hash table结构相似。
Hash
有一个关联的数组结构,和一些方法来操作这些数据。例如增加新的数据到
Hash
。
例如,
Hash.keys()
方法返回由所有的
Hash
的 key组成的一个数组(例如在我们的数据中的所有的年度)。
values()
方法返回的是所有值(CO2水平)的数组。
merge()
方法增加新的键值对到
Hash
中。
委派
我们的
CO2Levels
对象使用了委派的概念,在其中调用自己的
keys()
,
values()
, 和
add()
方法来委派这些实际的操作到一个内部的
Hash
对象。这个对象被作为一个局部变量:
levelsHash保存。
让我们看看
getYear()
方法:
getYear: function(year){
if (! isNaN(year)) {
return this.levelsHash[year];
} else {
return 377;
}
}
这个内置的
isNaN()
方法返回假,如果它的参数能够作为一个数值(如“2000”)计算的话,相反则是真(如
isNaN("hello")
)。如果
getYear()
参数通过了这个测试,那么代码使用一个普通的JavaScript表达式来返回一个键或属性的值:
this.levelsHash[year]
(
this.levelsHash["2004"]
计算结果为377.38)。浏览器于是在HTML
div
元素内部显示这个数值。
添加新的内容到现有的Hash
中
CO2Levels
对象有一个
add()
方法,能够增加新的键值对(额外的年度和CO2水平)到一个现有的数据中。
add: function(year,level){
var tmp = new Object();
tmp[year] = level;
this.levelsHash=this.levelsHash.merge(tmp);
}
这个方法根据它的两个参数(代表年度和CO2水平)创建了一个新的对象,这看起来像
{"2005":381}
。
代码再将这个对象传递到原型
Hash
对象的
merge()
方法。这个方法组合了一个新的对象到
Hash
的已有数据中,实质上是将它们合并到一个数据集团或关联数组。
merge()
方法返回已有的数据,但是一个新的属性/值对增加到了末尾。
通过一些重构,代码能够使用
XMLHttpRequest
从Mauna Loa Observatory上抓取新的数据,然后添加到已有的客户端数据中。
检测
最后,原型
Hash
对象也有一个
inspect()
方法。这个方法产生一个可读的
Hash
内容的显示。如图1-3:
图1-3 看看
Hash
的内部
CO2Levels
对象委派它自己的
inspect()
方法任务到一个内部的原型
Hash
对象。
inspect: function(){
alert( this.levelsHash.inspect());
}
这是一个非常有用的debug工具,用来察看
Hash
对象的当前内容。
后续的文章将要讨论在同样的应用中AJAX的缓存策略。它将介绍原型的
Ajax.Request
对象。它用来减少代码数量,并且专心使用
XMLHttpRequest
对象。这是一个重要的高级的JavaScript对象,在AJAX应用中,它在幕后产生HTTP连接。