对于 AJAX,在如何使用 JavaScript 的各种方面有很多选择。本文为创建 AJAX 组件的开发者提供了一些建议(侧重于在客户端使用 JavaScript,而在服务器上使用 Java)。其中的很多建议也适用于其他服务器端技术。这些建议如下所示:
element.innerHTML
与 Java 一样,标准化的样式可使代码易于使用和维护,尤其是在较大的组织中。Dojo 团队制订了一套非常完善的 JavaScript 编程约定。
信不信由您,可以使用面向对象的方式来编写 JavaScript。这样做可以获得更好的可重用代码,组织对象以及动态地装入对象。下面是 JavaScript 版本的购物车,其后是等效的 Java 代码。
function Cart() {
this.items = [];
}
function Item (id,name,desc,price)) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
// Create an instance of the cart and add an item
var cart = new Cart();
cart.items.push(new Item("id-1","paper","something you write on",5));
cart.items.push(new Item("id-1","Pen", "Something you write with", 3);
var total;
while (var l; l < cart.items.length; l++) {
total = total + cart.items[l].price;
}
上面的 Cart
对象为维护内部的 Item
对象数组提供了基本支持。购物车的等效 Java 对象表示形式如下所示。
import java.util.*;
public class Cart {
private ArrayList items = new ArrayList();
public ArrayList getItems() {
return items;
}
}
public class Item {
private String id;
private String name;
private String desc;
private double price;
public Item (String id, String name, String desc, double price) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
public String getId() {return id;}
public String getName() {return name;}
public String getDesc() {return desc;}
public float getPrice() {return price;}
}
以上的示例呈现了一个购物车的服务器端对象表示形式。此对象需要由一个 JSP、Servlet 或 JSF 受管 Bean 保存在 HttpSession
中。可以使用 AJAX 交互在购物车中添加商品或检索当前状态。
在 JavaScript 中,可能会出现对象名称发生冲突的情况。在 Java 中,可以使用包名称来防止出现命名冲突。与 Java 不同的是,JavaScript 并不提供包名称,但是您完全可以自己创建。在编写组件时,可使用对象和对象分层结构来组织相关对象以防止出现命名冲突。下面的示例创建了一个顶级对象 BLUEPRINTS
,从某种意义上讲,它相当于相关对象的名称空间。这些对象被设置为父对象的属性。
// create the base BLUEPRINTS object if it does not exist.
if (!BLUEPRINTS) {
var BLUEPRINTS = new Object();
}
BLUEPRINTS.Cart = function () {
this.items = [];
this.addItem = function(id) {
items.push(new Item(id);
}
function Item (id,qty) {
this.id = id;
this.qty = qty;
}
}
// create an instance of the cart and add an item
var cart = new BLUEPRINTS.Cart();
cart.addItem("id-1",5);
这种技术可以防止命名冲突,在可能发生命名冲突的地方使用这种代码是一种很好的做法。
原型属性是 JavaScript 的一项语言功能。所有对象都具有这种属性。如果在当前对象中找不到某个属性,JavaScript 中的属性解析功能就会查看原型属性的值。如果定义为原型对象的对象值不包含该属性,则会检查其原型属性的值。原型属性链(分层结构)通常用于在 JavaScript 对象中提供继承。下面的示例说明了如何使用原型属性在现有对象中添加行为。
function Cart() {
this.items = [];
}
function Item (id,name,desc,price)) {
this.id = id;
this.name = name;
this.desc = desc;
this.price = price;
}
function SmartCart() {
this.total;
}
SmartCart.prototype = new Cart();
SmartCart
扩展 Cart
对象,继承了它的属性并添加了一个 total 属性。接下来,我们在 Cart
中添加一些简便的函数,以添加商品并计算总价。虽然可以将函数直接添加到 SmartCart
对象中,但这会导致为每个 SmartCart
实例创建一个新函数。在 JavaScript 中,函数就是对象,因此,对于需要创建很多实例的对象,共享实例的行为可以节省资源。下面的代码声明了共享的 calcualteTotal
和 addItem
函数,并将它们添加为 SmartCart
原型成员的属性。
Cart.prototype.addItem = function(id,name,desc,price) {
this.items.push(new Item(id,name,desc,price));
}
Cart.prototype.calculateTotal = function() {
for (var l=0; l < this.items.length; l++) {
this.total = this.total + this.items[l].price;
}
return this.total;
}
// Create an instance of the cart and add an item
var cart = new SmartCart();
cart.addItem("id-1","Paper", "Something you write on", 5);
cart.addItem("id-1","Pen", "Soemthing you write with", 3);
alert("total is: " + cart.calculateTotal());
正如您所看到的那样,对象扩展是非常简便的。在本例中,只有一个 calcualteTotal
实例,addItem
函数被用于在 SmartCart
对象的所有实例间共享。请注意,items 的作用域仍然是 this.items
,即使这些函数是从 SmartCart
对象中单独声明的。在执行新的原型函数时,它们将位于 SmartCart
对象的作用域中。
在可能出现很多对象实例时,建议使用原型属性来定义共享行为,因为这会减少 JavaScript 中的对象数,并且可通过使用此属性将行为与数据完全分开。原型属性也非常适于为对象提供缺省值(此处不进行讨论)。还有许多其他的功能可以通过原型属性实现,但这已经超出了本文的讨论范围。有关详细信息,请参见本文的“资源”部分。
在前文的示例中,我们介绍了将购物车做为服务器对象和作为客户端对象的情况。在客户端存储状态还是在服务器上存储状态是一个难题。如果将 JavaScript 客户端设计为单页应用程序,则仅在客户端上使用购物车可能比较妥当。在这种情况下,JavaScript 版本的购物车可能仅在签出时与服务器进行交互。
一般说来,应使用 JavaScript 对象来存储与特定页面有关的视图状态。切记,JavaScript 对象是特定于 HTML 页面的,如果按下“重新装入”按钮、浏览器重新启动/崩溃或者导航到另一个页面,那么这些对象就会丢失。
当 Java 对象的作用域为 HttpSession
时,应在服务器上存储跨页面的状态。刷新页面和装入页面时,应使客户端和服务器端的对象保持同步。
如果需要脱机(例如,飞机旅行途中)开发 AJAX 客户端,可以使用多种方法在客户端上存储状态,但这些都不是标准的方法。Dojo 提供了 dojo.storage
API,可以在 JavaScript 客户端上存储最多 100KB 的脱机使用内容。随着客户端存储标准的出现,相信将对此 API 进行相应的修改以支持该标准。如果要保存的状态是保密的,或者需要由多台计算机访问该状态,则应考虑将状态保存在服务器上。
除非绝对必要,否则,不应将 JavaScript 绑定到一个特定组件上。切勿在可进行参数化的函数中对数据进行固定编码。下面的示例展示了一个可重用的 autocomplete JavaScript 函数。
<script type="text/javascript">
doSearch(serviceURL, srcElement, targetDiv) {
var targetElement = document.getElementById(srcElement);
var targetDivElement = document.getElementById(targetDiv);
// get completions based on serviceURL and srcElement.value
// update the contents of the targetDiv element
}
</script>
<form onsubmit="return false;">
Name: <input type="input" id="ac_1" autocomplete="false"
onkeyup="doSearch('nameSearch','ac_1','div_1')">
City: <input type="input" id="ac_2" autocomplete="false"
onkeyup="doSearch('citySearch','ac_2','div_2')">
<input type="button" value="Submit">
</form>
<div class="complete" id="div_1"></div>
<div class="complete" id="div_2"></div>
上面示例中的 doSearch()
函数可以被重用,因为它是使用元素的字符串 id、服务 URL 以及要更新的 <div> 进行参数化的。这个脚本可以被随后的其他页面或应用程序使用。
对象类型是使用花括号 ({}) 定义的对象,其中包含一组用逗号分隔的键值,与 Java 中的映射非常类似。
{key1: "stringValue", key2: 2, key3: ['blue','green','yellow']}
上面的示例展示了一个包含字符串、数字和字符串数组的对象类型。正如所想像的一样,对象类型是非常易于使用的,因为它可以被当作通用对象为函数传递参数。如果选择在函数中使用更多属性,函数签名并不会发生变化。请考虑使用对象类型作为方法参数。
function doSearch(serviceURL, srcElement, targetDiv) {
var params = {service: serviceURL, method: "get", type: "text/xml"};
makeAJAXCall(params);
}
function makeAJAXCall(params) {
var serviceURL = params.service;
...
}
还要注意,可通过使用对象类型来传递匿名函数,如下面的示例所示:
function doSearch() {
makeAJAXCall({serviceURL: "foo",
method: "get",
type: "text/xml",
callback: function(){alert('call done');}
});
}
function makeAJAXCall(params) {
var req = // getAJAX request;
req.open(params.serviceURL, params.method, true);
req.onreadystatechange = params.callback;
...
}
请不要将对象类型与使用类似语法的 JSON 相混淆。有关对象类型的详细信息,请参见下面的“资源”部分。
切勿将方案压缩与 ZIP 压缩相混淆,方案压缩指的是删除空白并缩短文件中变量和函数的名称。在部署应用程序时,请考虑压缩 JavaScript 资源。在开发模式下,应使脚本具有可读性,以便于对其进行调试。如果使用第三方 JavaScript 库,则请使用压缩版本(如果已提供)。通过使用压缩的脚本,可以大大节省带宽。例如,对于 Dojo 0.2.2,压缩的 dojo.js 为 130KB,而未压缩的版本为 208KB。Dojo 团队提供了一个名为 ShrinkSafe 的压缩服务,可使用该服务方便地压缩您自己的 JavaScript 文件。
如果通过服务器端组件从 JAR 文件中提供脚本或样式,则在创建 JAR 文件时不要使用 ZIP 压缩,因为这样一来服务器就必须为客户端的每个脚本或资源请求解压缩该文件。较大的脚本可能会导致服务器出现性能问题。
切勿将业务逻辑或服务器端访问代码放在 JavaScript 中。例如,在公开 JSF 方法/值绑定表达式(如 #{SomeBean.someMethod})或公开在服务器上调用的类/方法名称时,要特别小心,因为这会公开您的内部域模型,并且可能会被他人所利用。如果提供此类机制,请确保 JavaScript 客户端只能调用要提供给该客户端的方法。切勿将 SQL 语句放在 JavaScript 代码中。应始终牢记,在客户端只需单击一下按钮,即可看见 JavaScript 代码。
无论请求是否来自 AJAX 客户端,应始终在服务器上验证请求参数。
如果使用较大的库或一组库,在装入页面时,无需装入所有这些内容。可以在运行时使用库(如 JSAN)动态地装入 JavaScript,或者手动地使用 AJAX 装入 JavaScript 代码并在 JavaScript 上调用 eval()
进行装入。下面是一个 JavaScript 代码片段示例,它是在客户端动态装入的,用于创建 Cart
对象的实例。
cart.js
function Cart () {
this.items = [];
this.checkout = function() {
// checkout logic
}
}
现在,从代码中,对 cart.js 中的文本进行求值并创建一个 Cart
对象。
// get cart.js using an AJAX request
eval(javascriptText);
var cart = new Cart();
// add items to the cart
cart.checkout();
切记,在 javascriptText
中定义的对象仅在调用 eval(javascriptText);
时所在的上下文/作用域中可用。
虽然 XML 在 AJAX 中仍是有效的模型数据传输格式(尤其是与基于 XML 的服务进行通信以及服务还必须满足非基于 AJAX 的客户端的需求时),但应考虑使用 JSON 在服务器和基于 JavaScript 的客户端之间传输数据。以下 XML 文档表示两个产品类别,每个类别包含三种产品。
<categories>
<category id="0" name="Vegetables">
<products>
<product>
<name>Onion</name>
<price>.75</price>
</product>
<product>
<name>Carrot</name>
<price>.50</price>
</product>
<product>
<name>Eggplant</name>
<price>1.50</price>
</product>
</products>
</category>
<category id="1" name="Fruit">
<products>
<product>
<name>Orange</name>
<price>1.25</price>
</product>
<product>
<name>Apple</name>
<price>1.30</price>
</product>
<product>
<name>Tomato</name>
<price>.40</price>
</product>
</products>
</category>
</categories>
对于上面的 XML 文档,客户端需要大量 JavaScript 代码才能遍历 XML 文档 DOM,以及将数据绑定到 JavaScript 形式的对象表示形式。在代码中,需要定义类别和产品元素对象以及它们之间的关系。下面的示例展示了对上面的文档进行分析并将其绑定到对象表示形式时所需的 JavaScript。此示例使用一个 Category
对象数组,每个对象包含一个 Product
对象数组。
function Category (id, name) {
this.id = id;
this.products = [];
}
function Product (name,price) {
this.name = name;
this.price = price;
}
var categories = [];
// Callback with the XML returned from an AJAX request
function processResults(responseXML) {
var categoryElements = responseXML.getElementsByTagName("category");
for (var l=0; l < categoryElements.length; l++) {
var categoryElement = categoryElements[l];
var catId = categoryElement.getAttribute("id");
var catName = categoryElement.getAttribute("name");
var category = new Category(catId, catName);
var products = categoryElement.getElementsByTagName("product");
for (var pl=0; pl < products.length; pl++) {
var prodName = products[pl].getElementsByTagName("name")[0].firstChild.nodeValue;
var prodPrice = products[pl].getElementsByTagName("name")[0].firstChild.nodeValue;
category.products.push(new Product(prodName,prodPrice));
}
categories.push(category);
}
var veggies = categories[0].products[0];
}
正如在上面的示例中所看到的,提取 XML 文档并将其转换为客户端上的数据模型,这对客户端有一定的压力。
JSON 是一种简单的源自 JavaScript 的格式,它以简单的纯文本格式定义数据、对象定义以及关系。下面的代码示例使用 JSON 来表示与上面的 XML 文档相同的数据。
[
{"id": "0", "name":"Vegetables", "products":
[{"name": "Onion", "price": .75},
{"name": "Carrot", "price":.50},
{"name": "Eggplant", "price":1.50} ]},
{"id": "1", "name":"Fruit", "products":
[{"name": "Orange", "price": 1.25},
{"name": "Apple", "price":1.30},
{"name": "Tomato", "price":.40} ]},
]
要创建包含 Product
对象数组的 Category
对象的数组,只需使用一个 AJAX 调用对纯文本格式的 JSON 进行检索,并使用 JavaScript eval()
函数为其分配变量(在我们的示例中,此变量为 categories
)。
var jsonText = // get the JSON text listed above
var categories = eval(jsonText);
var veggies = categories[0].products[0];
这样便完成了!客户端没有 XML 处理代码。也就是说,服务器端逻辑将需要以 JSON 格式呈现数据。
一个功能丰富的 Web 应用程序的用户界面是由内容 (HTML/XHTML)、样式 (CSS) 和 JavaScript 组成的。JavaScript 是至关重要的,原因是用户操作(如鼠标单击)将调用它,并且它可以对内容进行处理。通过将 CSS 样式与 JavaScript 分开,可以使代码更易于管理和定制,并且可读性更高。建议将 CSS 和 JavaScript 放在单独的文件中。
如果从基于 Java 的组件(如 JSP、Servlet 或 JSF 呈现器)中呈现 HTML,您很可能需要在每页中输出样式和 JavaScript(如下所示)。
<style>如果在每个页面中都嵌入内容,则每次装入页面时,可能会产生带宽装入开销。通过引用内容而不是嵌入内容(如下所示),可以对 JavaScript 和 CSS 文件进行缓存并在不同页面中重用。
#Styles
</style>
<script type="text/javascript">
// JavaScript logic
<script>
<body>The quick brown fox...</body>
<link rel="stylesheet" type="text/css" href="cart.css">
<script type="text/javascript" src="cart.js">
<body>The quick brown fox...</body>
可以将链接映射到服务器上的静态资源,或者将其映射到动态生成资源的 JSP、Serlvet 或 JSF 组件上。如果开发的是 JSF 组件,则请考虑使用 Shale Remoting,它提供了一些基本类,这些类提供了用于编写脚本/CSS 链接的核心功能,并且甚至能够访问 JSF 组件的 JAR 文件中的资源。
在 JavaScript 代码中,请在动态更改元素样式时使用 element.className
而不是 element.setAttribute("class", STYLE_CLASS)
,因为绝大多数浏览器都支持 element.className
(这也是支持 IE 样式更改的最有效方法)。
在 JavaServer Faces (JSF) 和纯 Java 标记库的标记库定义中,不能使用属性 "class" 来指定样式,因为所有 Java 对象中都包含 getClass()
方法,并且无法覆盖该方法。请改用属性名称 "styleClass"。
像在其他源代码中一样,在 JavaScript 中应该使用最低限度的静态 HTML/XHTML 内容。这会使升级静态内容的管理变得更加容易。您可能希望在不更改源代码的情况下使组件内容具有可升级性。静态内容可以从代码中提取出,其方式类似于 Java 中使用 ResourceBundles
时的情况,也可以使用一个 AJAX 请求进行装入。这一设计提供了一种创建本地化界面的方法。
下面的 XML 文档 resources.xml
和代码片段展示了如何从 JavaScript 代码中提取静态资源。
<resources>
<resource id="foo">
<value>bar</value>
</resource>
<resource id="fooy">
<value>fooy</value>
<value>bar</value>
</resource>
</resources>
通过将 HTML 页面映射到 window.onload
函数装入 HTML 页面时,将装入 JavaScript 装入函数。
function load() {
// load the first set of images
// get resources.xml with an AJAX request
// and give the responseXML to processResults() }
function Resource (id, values) {
this.name = id;
this.value = values.join();
this.values = values;
}
var resources = new Object();
// parse the XML returned from an AJAX request
function processResults(responseXML) { var resourceElements =
responseXML.getElementsByTagName("resource");
for (var l=0; l < resourceElements.length; l++) {
var resourceElement = resourceElements[l];
var id = resourceElement.getAttribute("id");
var valueElements = resourceElement.getElementsByTagName("value");
var values = [];
for (var vl=0; vl < valueElements.length; vl++) {
var value = valueElements[vl].firstChild.nodeValue;
values.push(value);
}
resources[id] =new Resource(id,values);
}
alert ("foo=" + resources['fooy'].value);
}
此代码对上面的 XML 文档进行分析并将每个资源放入一个资源对象中。可以使用 JSON 发送相同的数据,然而,使用 XML 可能是较好的方法,此处,如果选择以 XML 形式发送本地化的内容,则便于提供一个方法以支持本地化的内容。有关详细信息,请参见在设计时牢记 I18n。
与 GUI 组件类似,当静态内容可能来自外部资源,在静态地设置布局尺寸时要非常小心,因为新内容可能会超过组件的边界。
element.innerHTML
您可能更愿意使用 element.innerHTML
而不是 DOM 样式 element.appendChild()
,因为 element.innerHTML
编程要容易得多,并且浏览器处理它的速度比使用 DOM API 要快得多。但一定要了解与这种方法有关的缺点。
如果选择使用 element.innerHTML
,请尝试编写生成最低限度的 HTML 的 JavaScript。应依靠 CSS 来改进表示形式。切记,应始终尽力将内容与表示形式分开。请确保在重新设置 element.innerHTML
之前,在元素的现有 innerHTML
中取消注册事件侦听程序,因为它会导致内存泄漏。切记,在替换内容时,element.innerHTML
内的 DOM 元素将会丢失,并且对这些元素的引用也会丢失。
请考虑使用 DOM API(如 element.appendChild()
)在 JavaScript 中动态创建元素。DOM API 是标准的 API,在创建元素时它们以编程方式对这些作为变量的元素进行访问,并且更易于避免使用 element.innerHTML
时遇到的诸如内存泄漏或丢失引用等问题。这就是说,切记使用 DOM 在 IE 中创建表可能会出现问题,因为 IE 中的 API 并不遵循 DOM。有关所需的 API,请参见关于表的 MSDN 文档。在 IE 中添加表以外的元素不会出现问题。
像 Java 一样,JavaScript 也使用垃圾回收;而且您同样需要牢记垃圾回收并不是完善的内存管理解决方案。请确保在不再需要变量时取消对它的引用,以便进行垃圾回收。如果使用的是 element.innerHTML
,请确保取消引用代码中的任何侦听程序,然后再对其进行替换。
事件处理代码容易出现内存泄漏。请考虑使用事件包装(如 Dojo 或MochiKit 中提供的事件包装),因为它们就是为防止出现泄漏而设计的。
关闭易于创建,但在某些情况下,可能会导致出现问题。习惯了面向对象的开发者可能倾向于将对象的相关行为与对象绑定在一起。下面的示例展示了一个将导致内存泄漏的关闭。
function BadCart() {
var total;
var items = [];
this.addItem = function(text) {
var cart = document.getElementById("cart");
var itemRow = document.createElement("div");
var item = document.createTextNode(text);
itemRow.appendChild(item);
cart.appendChild(item);
itemRow.onclick = function() {
alert("clicked " + text);
}
}
}
那么,此示例到底错在什么地方呢?addItem
引用的是不在 BadCart
作用域中的 DOM 节点。匿名 "onClick" 处理程序函数将一直保留对 itemRow
的引用,不允许对其进行垃圾回收,除非我们显式地将 itemRow
设置为 null
。GoodCart
中的以下代码将会解决此问题:
function GoodCart() {
var total;
var items = [];
this.addItem = function(text) {
var cart = document.getElementById("cart");
var itemRow = document.createElement("div");
var item = document.createTextNode(text);
itemRow.appendChild(item);
cart.appendChild(item);
itemRow.onclick = function() {
alert("clicked " + text);
}
itemRow = null;
item = null;
cart = null;
}
}
对 itemRow
进行设置,将删除设置为 itemRow.onclick
处理程序的匿名函数对它的外部引用。一种很好的做法是,进行清理并将 item
和 cart
设置为 null
以便对所有内容进行垃圾回收。另一个防止内存泄漏的解决方案是,不要使用外部函数来替代 BadCart
中的匿名函数。
如果使用关闭,切勿使用关闭的局部变量保留对浏览器对象(如与 DOM 相关的对象)的引用,因为这可能会导致内存泄漏。应在删除对象的所有引用后,再对对象进行垃圾回收。有关关闭的详细信息,请参见 Javascript 关闭。
提供一种覆盖装入资源的方法,这些装入资源包括 JavaScript 为中心的、JSP、Servlets 或 JSF 组件所使用的独立的脚本、CSS 文件或图像。如果对组件进行 JAR 分发时组件中包括 JSP 标记或 JSF 组件,则请将这些资源文件移到 WEB-INF 目录中,并创建代码使它们以流的方式传给客户端。在 JSF 中,可使用阶段侦听程序以及其他 Java Web 技术(例如 Servlet 和过滤器)来实现此操作。通过这种方式,可以在组件开发过程中方便地修改组件的样式和 JavaScript,而无需重新打包和部署组件。您的客户将获得一个简明实用的包,而无需担心在其自己的 Web 应用程序中部署资源文件的问题。这种解决方案可以使客户逐个定制组件样式或 JavaScript,而无需重新生成组件和重新进行打包。
对于 HTML 页面,设置为 UTF-8 编码可支持的语言种类最多。
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
在传递本地化的表单字段变量时,请使用 JavaScript encodeURIComponent()
函数将字段变量转换为 HTTP 友好的表示形式。使用的编码将与 HTML 页面内容类型的编码相匹配。下面的示例说明了如何使用 id "complete-field" 编码表单输入字段的值,并使用 Dojo 发出 AJAX 请求。
var completeField = document.getElementById("complete-field");
var bindArgs = {
url: "autocomplete?action=complete&id=" + encodeURIComponent(completeField.value),
mimetype: "text/xml",
load: function(type, data) {
processSearch(data);
}
};
dojo.io.bind(bindArgs);
请考虑使用 XML 作为本地化数据返回的内容的类型,因为 XML 指定的编码信息可以被 XMLHttpRequest 对象识别和应用。如果传递本地化的内容,则需要确保服务器正确地对返回内容进行编码。有关可以在服务器上执行哪些操作的详细信息,请参见如何提供国际化的 AJAX 交互?。
请参见 Dojo 团队编写的 JavaScript 样式指南:
http://dojotoolkit.org/docs/js_style_guide.html
有关面向对象的 JavaScript 的详细信息,请参见以下链接
http://www.cs.rit.edu/%7Eatk/JavaScript/manuals/jsobj/index.htm要了解如何模拟名称空间,请参见:
"http://www.justatheory.com/computers/programming/javascript/emulating_namespaces.html要了解 JSON,请参见:
要了解 JavaScript 对象类型,请参见:
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide:Literals
要了解有关 JavaScript 关闭的详细信息,请参见:
http://jibbering.com/faq/faq_notes/closures.html
要了解有关 JavaScript 的各种提示,请参见 QuirksMode:
有关将 CSS、内容和 JavaScript 分开的讨论,请参见:
http://thinkingandmaking.com/entries/63