构建完备的Ajax开发工具箱

构建完备的Ajax开发工具箱

作为一个有经验的Web应用开发人员,也许你可以熟练地应用某种服务器端技术(或者,应用多种服务器端技术)来构建Web应用。我们已经看到,在过去几年中,服务器端有了长足的发展,服务器端软件开发越来越容易,也越来越健壮,相比之下,客户端基本上被抛在了一边。Ajax技术的横空出世使这种状况有所改观,因为开发人员现在有了一个更丰富的客户端工具箱,有大量工具可以使用。你可能不习惯使用大量的HTML、JavaScript和CSS,但是要实现Ajax技术的话,你就必须这么做。使用这一章将介绍的工具和技术,开发Ajax应用会更为容易。这一章不是一个深入全面的教程,只能作为这些有用工具的快速入门。

 

使用JSDoc建立JavaScript代码的文档

像其他的许多编程语言一样,在一般的软件开发人员看来,JavaScript也有一个基本的缺陷:编写(或者重新编写)一个功能通常相对容易,但是要阅读现有的代码,并明确它是如何工作的, 就不那么轻松了。编写代码时可以适当地增加注释,这样当其他开发人员要理解代码如何工作,特别是要修改代码的功能时,就能减轻他们的负担,节省他们的时间和精力。

Java语言引入了一个工具,名为javadoc。这个工具可以根据源代码中的文档注释以HTML格式生成API文档。所生成的HTML文档在任何Web浏览器上都能阅读,而且由于它是以HTML格式生成的,所以可以在线发布,这样开发人员就能很容易地访问这些文档。要以一种可以轻松浏览的格式来提供API文档,这样开发人员就不必仔细地查看源代码才能了解某个类或方法会有怎样的行为,以及该如何使用。

JSDoc则是面向JavaScript的一个类似的工具(jsdoc.sourceforge.net)。JSDoc是一个开源工具,得到了GNU Public License(GPL)的许可。JSDoc用Perl编写,这意味着Windows用户必须先安装一个Perl运行时环境。(而对于大多数Linux和Unix操作系统,Perl都会作为其中的一个标准部分。)

 

安装

要使用JSDoc,Windows用户必须安装一个Perl环境,如ActivePerl(www.activeperl.com)。还必须安装一个非标准的Perl模块,名为HTML::Template(www.cpan.org)。JSDoc项目网页提供了有关说明,如果需要帮助可以参考。

JSDoc发布为一个压缩的tarball。要安装JSDoc,只需从JSDoc项目网页下载tarball,把它解开到你指定的目录。进入JSDoc目录,输入以下命令,就能测试JSDoc了:

perl jsdoc.pl test.js

JSDoc把所得到的HTML文件发送到一个名为js_docs_out的目录。打开这个文件夹中的index.html文件,就可以浏览根据test.js文件生成的文档。

用法

既然对JSDoc已经有所了解,你可能想知道如何使用JSDoc来为你的JavaScript代码生成文档。表5-1列出了可以创建HTML文档的一些特殊JSDoc标记。如果你曾在Java代码中编写过javadoc注释,应该对这些标记并不陌生。如果要在最后生成的文档中包含某个注释块,所有这些注释块都必须以/**开头,并以*/结束。

5-1  JSDoc 命令属性

命令名

描述

@param

@argument

指定参数名和说明来描述一个函数参数。

@return

@returns

描述函数的返回值。

@author

指示代码的作者。

@deprecated

指示一个函数已经废弃,不建议使用,而且在将来版本的代码中可能会彻底删除。要避免使用这段代码。

@see

创建一个HTML链接指向指定类的描述。

@version

指定发布版本。

@requires

创建一个HTML链接,指向这个类所需的指定类。

@throws

@exception

描述函数可能抛出的异常的类型。

{@link}

创建一个HTML链接,指向指定的类。这与@see很类似,但{@link}能嵌在注释文本中。

@author

指示代码的作者。(译者注:这个标记前面已经出现过,建议去掉)

@fileoverview

这是一个特殊的标记,如果在文件的第一个文档块中使用这个标记,则指定该文档块的余下部分将用来提供文件的一个概述。

@class

提供类的有关信息,用在构造函数的文档中。

@constructor

明确一个函数是某个类的构造函数。

@type

指定函数的返回类型。

@extends

指示一个类派生了另一个类。通常JSDoc自己就可以检测出这种信息,不过,在某些情况下则必须使用这个标记。

@private

指示一个类或函数是私有的。私有类和函数不会出现在HTML文档中,除非运行JSDoc时提供了—private命令行选项。

@final

指示一个值是常量值。要记住JavaScript无法真正保证一个值是常量。

@ignore

JSDoc会忽略有这个标记的函数。

 

 

JSDoc发布包中包括一个名为test.js的文件,这是一个很好的参考例子,可以从中了解如何使用JSDoc。应该记得,第一次测试JSDoc安装是否成功时就是根据这个文件来创建文档文件(译者注:原文称“这就是第一次测试JSDoc安装是否成功时创建的文档文件”,这种理解有误,test.js显然不是文档文件,所得到的文档文件应当是js_docs_out的目录下的index.html)。如果对如何使用JSDoc标记还有疑问,可以参考这个文件。

代码清单5-1是一个小例子,这里展示了JSDoc的用法。jsDocExample.js定义了两个类:Person和Employee。Person类有一个属性name,还有一个方法getName。Employee类继承自Person,并增加了title和salary属性,另外还增加了一个方法getDescription。

代码清单5-1. jsDocExample.js

/**

* @fileoverview This file is an example of how JSDoc can be used to document

* JavaScript.

*

* @author Ryan Asleson

* @version 1.0

*/

/**

* Construct a new Person class.

* @class This class represents an instance of a Person.

* @constructor

* @param {String} name The name of the Person.

* @return A new instance of a Person.

*/

function Person(name) {

/**

* The Person's name

* @type String

*/

this.name = name;

 

/**

* Return the Person's name. This function is assigned in the class

* constructor rather than using the prototype keyword.

* @returns The Person's name

* @type String

*/

this.getName = function() {

return name;

}

}

/**

* Construct a new Employee class.

* @extends Person

* @class This class represents an instance of an Employee.

* @constructor

* @return A new instance of a Person.

*/

function Employee(name, title, salary) {

this.name = name;

/**

* The Employee's title

* @type String

*/

this.title = title;

/**

* The Employee's salary

* @type int

*/

this.salary = salary;

}

/* Employee extends Person */

Employee.prototype = new Person();

/**

* An example of function assignment using the prototype keyword.

* This method returns a String representation of the Employee's data.

* @returns The Employee's name, title, and salary

* @type String

*/

Employee.prototype.getDescription = function() {

return this.name + " - "

+ this.title + " - "

+ "$" + this.salary;

}

 

虽然不像JSDoc发布包中的test.js文件那么完备,这个例子同样很好地展示了JSDoc最常见的用法(见图5-1)。 @fileoverview 标记提供了jsDocExample.js的一个概述。@class标记描述了两个类,@constructor标记将适当的函数标记为对象的构造函数。@param 标记描述了一个函数的输入参数,@returns和@type标记描述了函数的返回值。这些标记是你最有可能用到的,而且对于浏览文档的其他开发人员,这些标记也最有用。

图见P135-1

5-1 JSDoc根据jsDocExample.js文件生成的文档

 

使用Firefox扩展验证HTML内容

当前的浏览器都能很好地实现标准W3C DOM。只要创建的内容能遵循标准HTML或XHTML,就几乎能得到所有浏览器的支持。不过通常说起来简单,做起来就不那么容易了。不同于C++或Java这样的编译语言,HTML没有编译器可以将人可读的代码翻译为机器可读的二进制代码。要由Web浏览器将人可读的HTML或XHTML代码解释成DOM的一个内部表示,并适当地将这个内容展现在屏幕上。

20世纪90年代末的浏览器之争使得浏览器开发商(如Microsoft和Netscape)纷纷增加了一些专用的HTML标记,以扩大自己的市场份额。出于这个原因,再加上HTML没有一个严格的编译器,就导致了大量非标准Web页面的出现。当前的浏览器尽管支持最新的W3C标准,不过会尽可能地宽容,即使是写得不好的HTML页面也能得到“通融”。根据HTML页面的doctype(如果有的话),大多数浏览器都有两种呈现模式:strict(严格)和quirks(怪异)。doctype指示一个Web页面确实遵循某个W3C推荐规范(如HTML 4.1或XHTML 1.0)时,Web浏览器就会使用strict(严格)模式,如果没有doctype,或者页面与指定的doctype有很多冲突,Web浏览器就使用一种quirks(怪异)模式。

作为一个开发人员,应当尽力创建遵循某种W3C标准的页面。这么做很有好处,不仅在所有现代Web浏览器上都能访问你的Web页面,而且由于浏览器可以根据HTML代码创建一个正确的DOM表示,这也能让你的日子更好过。如果页面写得不好,浏览器可能无法创建DOM的正确表示,浏览器就会以一种怪异模式来呈现页面。如果DOM表示不正确,则很难通过JavaScript来访问和修改DOM,特别是无法以一种跨浏览器的方式来访问。

由于HTML没有一个严格的编译器,怎么确保你写的HTML代码遵循W3C标准呢?幸运的是,Firefox Web浏览器已经有几个扩展工具,可以很容易地验证你的Web页面。

HTML Validator

HTML Validator[1]是一个Firefox扩展工具,它能查找并标志出HTML页面上的错误。HTML Validator以Tidy为基础,这是原先W3C为验证HTML代码开发的一个工具。HTML Validator把Tidy工具嵌在Firefox中,这样就能在浏览器中验证页面的源代码,而不必把代码发给一个第三方进行验证。

Tidy会查找HTML错误,并把这些错误归为3类:

            错误(Errors): Tidy无法修正或理解的问题。

            警告(Warnings)Tidy可以自动修正的错误 。

            可达性警告(Accessibility warnings):这些HTML警告对应W3C Web可达性发起组织(Web Accessibility Initiative,WAI)定义的三个优先级。

HTML Validator会在浏览器的右下角显示页面的状态以及错误个数,从而能在开发周期中提供很快的反馈(见图5-2)。

图见P136-1

5-2 HTML Validator使用状态条上的一个图标来总结页面上的错误。

 

如果选择View àPage Source菜单项查看Web页面的源代码,HTML Validator还能提供更大的帮助。Firefox的view-source(查看源代码)窗口会正常打开,不过还将启用HTML Validator,这个窗口中包括两个新的窗格(见图5-3)。HTML Errors and Warnings(HTML错误和警告)窗格会列出在页面中找到的所有错误。点击列表中的任何错误项,就会使源代码主窗口跳到HTML源代码中有问题的位置。Help(帮助)窗格会详细地描述这个问题,并提供一些建议告诉你如何修正这个问题。

 

图见P137-1

5-3  查看页面的源代码时,HTML Validator会列出HTML源代码中的错误,并提出修正问题的建议

 

Firefox的view-source窗口包括一个Clean Up the Page(清理页面)按钮。点击这个按钮会打开一个窗口,这个窗口能进一步帮助你修正页面上的错误(见图5-4)。Cleanup the Page(清理页面)窗口打开后,最上面有4个标签: Cleaned Html(清理后的HTML)、Original Html(原来的HTML)、Cleaned Browser(清理后的浏览器)和Original Browser(原来的浏览器)。

Cleaned Html标签对Web开发人员最有用。这个标签列出了通过HTML Validator修正后页面的源代码。HTML Validator会尽其所能自动修正页面上的所有错误,修正后的输出就列在这个标签下。Original Html标签会列出页面原来的源代码,也就是在HTML Validator处理之前的形式。

有时,修正页面上的HTML错误可能会改变浏览器呈现页面的模式,有时这正是我们需要的,但有时可能并不希望有这种结果。Cleaned Browser标签会显示使用HTML Validator提供的源代码时,页面会是什么样子,而Original Browser标签则显示使用原来的源代码时相应的页面。

总之,HTML Validator是一个强大的工具,可以帮助你清理HTML,使之遵循W3C标准和推荐规范。遗憾的是,HTML Validator只能在Windows平台上使用。好在,还有另一个与HTML Validator有类似功能的Firefox扩展工具,而且在所有平台上都可以使用。

 

图见P138-1

5-4  HTML Validator 的Cleanup the Page对话框会给出新的源代码,在此修正了在原来的源HTML中发现的错误。

 

Checky

Checky[2] 是另一个Firefox扩展工具,可以帮助开发人员编写更好的HTML页面。HTML Validator会在本地验证源代码,与此不同,Checky则把页面源代码发送给不同的第三方网站来完成HTML验证。

Firefox中,右键点击任何页面,并选择Checky菜单项(见图5-5)就可以访问Checky。Checky菜单项包含多个子菜单项,分别完成不同的任务。HTML/XHTML菜单项会列出可以提供HTML验证服务的多个网站。点击此列表中的任何网站,就会在Firefox中打开一个新的标签,指向这个验证网站。Checky会自动地填入要验证的页面的地址,并开始验证过程。

如图5-6所示,要验证的代码必须能够在Internet上得到,这样验证网站才能访问到其HTML。

通过Checky还可以访问其他网站,这些网站不光能验证HTML。Links(链接)菜单会列出能验证页面上所有链接的网站,以确保所有链接连接的URL都确实存在。CSS菜单列出的网站能验证页面上使用的所有CSS文件,以确保这些CSS文件遵循标准CSS规则。

应当花点时间来测试Checky提供的验证网站。通过使用这些验证工具,可以使你的代码更能与标准兼容,而且可以减少手工跟踪问题花费的时间。

 

图见P139-1

5-5  可以在Firefox中通过一个上下文快捷菜单来访问Checky。

 

图见P139-2

5-6 通过Checky,使用W3C的在线验证器得到的HTML验证结果

 

 

使用DOM Inspector搜索节点

Mozilla Suite和Firefox浏览器中打包了一个DOM Inspector工具。利用DOM Inspector,可以查看Web页面的结构化表示,甚至能搜索某些特定的节点,并自动更新DOM中的节点。在Firefox中,可以通过Tools菜单项来访问DOM Inspector。要使用DOM Inspector来检查一个Web页面,需要在文本框中输入所需的URL,并点击Inspect框;或者也可以从File à Inspect a Window菜单选择一个窗口,这就会列出当前在浏览器中打开的Web页面(见图5-7)。

DOM Inspector主窗口有3个窗格。左上窗格是Web页面DOM的一个层次结构视图。根元素往往是文档本身,Web页面中的每个节点都列在这个根元素下面。对于大多数Web页面,根节点几乎都是HTML。如果在结构化视图窗格中选择了一个节点,右上窗格会给出这个节点的详细信息。如果窗口下部没有打开一个浏览器窗口,可以选择View à Browser菜单项打开一个浏览器窗口。

DOM Inspector是一个功能很强大的工具,利用这个工具,你可以快速地遍历给定Web页面的结构,并修改Web页面DOM中的各个节点。 通常可以在结构化视图中手工地查找节点。也可以使用Search àFind Nodes菜单项来查找各个节点。利用这个搜索功能,你可以根据ID属性、标记名或属性名和值来查找节点(见图5-8)。

 

图见P140-1

5-7  DOM Inspector主窗口

 

图见P141-1

5-8 DOM Inspector的 Find Nodes对话框

 

要在DOM Inspector中查找节点,最容易的方法是使用鼠标。在结构化视图中查找一个节点时,可以选择Search à Select Element by Click菜单项,并点击浏览器窗口中的一项。所选项会以红色边框突出显示,而且结构化视图窗格中将选中相应的节点。一旦在结构化视图窗格中选中一个特定节点,就可以开始检查和修改它的属性了。例如,可以右键点击一个节点,从上下文快捷菜单选择Cut,再选择结构化视图窗格中的另一个节点,右键点击,从上下文快捷菜单选择Paste。这样就能在DOM中将所选节点有效地从一处移到另一处。图5-9显示了使用这种方法可以将Google搜索页面上的主图片移到页面的另一个部分。

图见P141-2

5-9 使用DOM Inspector移动Google搜索页面主图片的结果

 

还能在右上方的信息窗格中发现更多功能。对于结构化视图窗格中选中的节点,会在这个窗口显示有关该节点的各类信息。可以使用上方标题区中的下拉列表图标切换信息的类型。可选的信息类型包括DOM Node、Box Model、XBL Bindings、CSS Style Rules、Computed Style和JavaScript Object。使用Mozilla的XML用户界面语言(XML User Interface Language,XUL)工具包开发应用时,Box Model和XBL Bindings信息类型更有用。

DOM Node信息类型会显示有关节点的基本信息,如其标记名、节点值,以及节点的属性。右键点击一个节点,会显示一个上下文快捷菜单,选择其中的Edit菜单项,就可以修改节点属性的值。例如,可以选择一个font(字体)节点,修改size(大小)属性。如图5-10所示,使用这种技术,可以增大Google搜索页面中输入框上方的字体大小。

图见P142-1

5-10 使用DOM Inspector,动态修改输入框上方的字体大小

 

JavaScript Object信息类型会列出所选节点可用的DOM属性和方法。如果要确定一个特定DOM节点有哪些可用的属性和方法,这就是一个很有用的特性。例如,除了一般的正常方法外(如appendChild),对于表格节点还会列出诸如insertRow和deleteRow等方法。

如果设置为JavaScript Object信息类型,在信息窗格中右键点击就会显示一个带有Evaluate JavaScript菜单项的上下文快捷菜单。选择这个菜单项,则会弹出一个窗口,可以针对所选节点计算一个JavaScript表达式。图5-11显示了针对Google搜索页面的body(体)节点打开的JavaScript计算窗口(译者注:原文此处为“菜单”,有误),可以看到,如果执行计算窗口所示的JavaScript表达式,就会在页面的最后追加指定的文本。注意target用作为变量名,它指示所选的节点,在这里就是body元素。

CSS Style Rules和Computed Style信息类型会显示所选节点样式规则的有关信息。Computed Style 信息类型会列出浏览器呈现引擎看到的所有与样式相关的属性,包括使用样式属性显式设置的样式,在外部CSS文件中指定的样式,或者从父节点继承的样式。

前面已经简要地了解了DOM Inspector的特性,可以想见,在你的开发环境中,这必将是一个非常有用的工具。可以使用DOM Inspector来检查通过document.createElement方法动态创建的DOM节点,以确保有所需的属性值。如果一个特定节点没有应用你希望的样式规则,也可以使用DOM Inspector来找出原因。随着越来越熟悉DOM Inspector的功能,肯定会发现DOM Inspector在你的Web开发过程中将是一个举足轻重的强大工具。

图见P143-1

5-11 使用 JavaScript计算窗口向页面的体动态增加一个文本节点(左图)以及浏览器窗格中的结果(右图)

使用JSLint完成JavaScript语法检查

JSLint是一个JavaScript验证工具(www.jslint.com),可以扫描JavaScript源代码来查找问题。如果JSLint发现一个问题,就会显示一个消息加以描述,并指出这个错误在源代码中的大致位置。有些编码风格约定可能导致未预见的行为或错误,JSLint除了能指出这些不合理的约定,还能标志出结构方面的问题。尽管JSLint不能保证逻辑一定正确,但确实有助于发现错误,这些错误很可能导致浏览器的JavaScript引擎抛出错误。

JSLint定义了一组编码约定,这比ECMA定义的语言更为严格。这些编码约定汲取了多年来的丰富经验,并以一条年代久远的编程原则作为宗旨:能做并不意味着应该做。JSLint会对它认为有风险的编码实践加标志,另外还会指出哪些是明显的错误(见图5-12),从而促使你养成好的JavaScript编码习惯。

 

图见P144-1

5-12 JSLint会检查错误以及不好的编码风格,以此提供JavaScript验证

 

JSLint可能会把一些结构方面的错误标志为可疑的编码实践,以下列出了其中一部分。(完整的列表可以参考JSLint的文档)。

             JSLint要求所有代码行都以分号结束。尽管JavaScript确实允许将换行符作为行结束符,但一般认为这种做法是不明确的,而且是不好的编码风格。

             使用if和for的语句必须使用大括号把语句块括起来。

            不同于其他编程语言,在JavaScript中,块不会作为变量的作用域。JavaScript只支持函数级作用域。因此,JSLint只接受作为function、if、switch、while、for、do和try语句一部分的块,其他的块都会标志为错误。

             变量只能声明一次,而且在使用之前必须先声明。

             JSLint会把出现在return、break、continue或throw语句后面的代码标志为不可达的代码。这些语句后面必须紧跟着一个结束大括号。

 

 

对于JavaScript程序员新手来说,JSLint是一个非常好的工具,因为它会教你一些好的JavaScript编码实践。由于JSLint能把可能导致逻辑错误或其他未预见行为的部分标出来,因此可以减少调试时间。如果你调试一段JavaScript代码时遇到困难,可以找JSLint帮忙。

完成JavaScript压缩和模糊处理

我们都知道,JavaScript是一种解释型语言,会在客户的浏览器中执行。换句话说,JavaScript会以明文下载到浏览器,再由浏览器根据需要执行这个JavaScript代码。

用户只要使用浏览器的查看源代码功能就能读到JavaScript源代码,这会显示出页面的完整HTML标记,包括所有JavaScript块。即使JavaScript源代码放在一个外部文件中,并用script(脚本)标记的src属性来引用,用户也可以下载并阅读JavaScript源代码。由于查看页面的人总能得到JavaScript源代码,所以不要把专用或机密的逻辑算法放在JavaScript中。这种逻辑最好放在服务器上,在这里会更安全一些。

在基于Ajax的应用中,随着JavaScript的使用越来越多,JavaScript文件的大小可能会成为问题。由于这是一种解释型语言,JavaScript不会编译为机器级的二进制码,而对于可执行代码来说,二进制码才是更高效的存储格式。如果JavaScript文件太多,就会使应用的速度慢下来,因为它需要先把源代码从服务器下载浏览器,然后才能在浏览器上执行。另外,如果要用诸如JSDoc(如前所述)等工具,为此要对代码加注释(译者注:原文这里说“要用JSDoc等工具对代码加注释”,这是不对的,因为JSDoc并非用于加注释,而是根据注释来生成文档),本来就很大的JavaScript代码会变得更大。

你可能看到了,JavaScript没有二进制的可执行包,这会带来两个问题:安全性差,还需要下载大量的源代码。有没有办法避开这些问题呢?

JavaScript日益普及,因此也产生了许多工具,这些工具有助于解决这些问题。最简单的压缩工具会把JavaScript源代码中的所有注释和换行符去掉,这样可以减少下载的源代码的大小。删除注释行和换行符能使JavaScript文件的大小缩小30%甚至更多,这要依具体情况而定。需要说明,JavaScript源代码中的所有语句都必须正确地以分号结束,只有这样才能用这种工具对源代码进行压缩。如果没有做到这一点,就会得到错误,或者未预料到的行为。所以,在压缩JavaScript源代码之前,一定要使用JSLint确保所有语句都以分号结束!

还有一些工具则更进一步,可以提供模糊(obfuscation)服务。模糊(Obfuscation)过程是指,全面扫描源代码,把字段和函数名从原来的名字改成经编码的无意义的名字,以防止其他人了解源代码的含义和内部工作。对于能编译为机器级二进制指令的语言来说,如C++,一般不需要这种模糊处理。就算是Java和C#这样的现代语言,它们会编译为中间字节码而不是二进制指令,也需要模糊工具来保证最大程度的安全。JavaScript作为一个完全解释型语言同样需要这样一种工具。

有一个能同时提供压缩和模糊服务的免费工具,这就是MemTronic的HTML/JavaScript Cruncher-Compressor (hometown.aol.de/_ht_a/memtronic/)。这个工具支持多个层次的JavaScript压缩。最低层次的压缩在这个工具中称为挤压(crunching),只是删除所有注释和换行符。这个工具的相关文档称,这样可以节省20%到50%的带宽。使用“crunch”模式,可以看到JavaScript文件的大小能缩小30%。

最高层次的压缩在这个工具中就称为压缩(compressing),是用一种真正的压缩机制具体压缩JavaScript源代码,并向文件增加自动解压缩功能。这个工具称,使用这种模式时,带宽能减少40%到90%,而且压缩后的输出已经在当前版本的Internet Explorer、Netscape、Mozilla和Opera等浏览器上成功通过测试。同样用原来使用“挤压”模式测试的JavaScript文件,我们又用“压缩”模式做了测试,这一次发现文件大小的缩小幅度超过了65%(见图5-13)。

写这本书时,MemTronic工具的文档称,JavaScript的模糊工具还不算完备。不过,可以看看图5-13所示的输出窗口。这是对一个JavaScript文件执行“压缩”操作的结果。这个输出很难阅读,实际上其中包含了许多奇怪的字符。尽管这可能不是真正意义上的模糊处理,但确实足以防止有不良企图的用户查看(甚至窃取)你的JavaScript源代码。

图见P146-1

5-13 MemTronic的 HTML/JavaScript Cruncher-Compressor可以大大缩小JavaScript源代码的大小,并让人很难读懂。

 

 

使用Firefox的Web开发扩展包

Firefox的Web开发扩展包(Web Developer extension for Firefox)为Firefox浏览器增加了大量有用的Web开发工具。一旦安装了这个扩展包,就可以通过一个工具条来访问为浏览器增加的这些工具(见图5-14)。在目前能够运行Firefox的所有平台上,都能使用这个扩展包,这说明在Windows、OS X和Linux都能顺利地使用这个扩展包。Firefox的Web开发扩展包可以从chrispederick.com/work/firefox/webdeveloper/获得。

Web开发扩展包提供了至少80个工具,可以完成各种任务,从把GET请求转换为POST请求(或反之),到允许动态编辑页面的CSS规则等等都有涉及。由于有太多的工具,因此不便于一一列出,以下只是列出了总的工具种类:

           Disable菜单可以禁用浏览器的一些功能,如JavaScript、CSS、cookies和动画图片。

           CSS菜单包含了与CSS规则和样式表相关的工具。

           可以使用Forms菜单把GET请求转换为POST请求(或反之),自动填写表单值,以及删除输入元素的最大长度。

           可以使用Images菜单中的功能显示图片略图或隐藏图片。

           可以使用Information菜单的功能检查与页面相关的各种信息,如cookie信息、链接信息和响应首部。

           Miscellaneous菜单提供的工具可以清空浏览器的缓存、历史和会话cookie,还可以放大或缩小页面。

           可以使用Outline菜单概要列出表格、表单元、框架、块级元素等等。

           Resize菜单会在标题栏中显示当前窗口的大小,以及其他可以调整当前窗口大小的工具。

           利用Tools菜单下的工具可以找到验证CSS、HTML和下载速度的第三方网站的快速链接。

           利用View Source按钮可以很容易地查看页面的源代码。

           利用Options菜单,可以定制编辑Web开发扩展包的颜色、快捷键和行为。

有些Web开发人员对Web开发扩展包提供的工具和功能大加赞赏,称之为“不可缺少的”,“最好的”,“至关重要的”。可以安装这个扩展包,尝试一下各种工具,看看它对你的开发和调试过程是否有帮助。

图见P147-1

5-14  Web开发扩展包为Firefox增加的工具条。

 

实现高级JavaScript技术

在此假设这本书的读者对JavaScript至少有基本的实际认识。如果要提供JavaScript的全面教程,这本身就需要一本完整的书才能讲清楚,所以在这里我们不打算详细介绍这种语言。相反,这一节只是讨论JavaScript的一些可能鲜为人知的高级特性,并说明如何在你的Ajax开发中结合使用这些特性。

首先,我们先来简单地谈谈JavaScript的历史,以便你了解它原来是什么样子,又是怎么发展到今天的。Netscape的Brendan Eich于1995年开发了JavaScript。他的任务本来是开发一种办法,使得创建和维护网站的非专业Web设计人员能够更容易地开发Java applet。Eich认为,应当开发一种不需要编译器的弱类型语言,这应该是一个合适的选择。

Eich开发的这个语言原来有过很多名字,不过,由于后来Java在市场上大获成功,为了借这股东风,最后还是改名为JavaScript。JavaScript很快成为Web上最流行的脚本语言,这要归功于它的低门槛,另外还因为能够把JavaScript脚本从一个页面复制到另一个页面。在JavaScript和Navigator DOM的早期版本基础之上,产生了DOM Level 0标准,将表单元素和图像定义为DOM元素的子元素。

Microsoft迎头赶上,创建了自己的脚本语言VBScript。VBScript在功能上与JavaScript是类似的,但采用了类Visual Basic语法,而且只能用于Internet Explorer。Microsoft还提供了JavaScript的一个实现JScript(现在已由ECMA标准化并称为ECMAScript)。尽管不同JavaScript的语法几乎是一样的,但不同浏览器上DOM实现却大相径庭,以至于几乎不可能创建跨浏览器的脚本。使用“最小分分母”方法得到的脚本通常只能做最简单的任务。

到了1998年,Netscape开放了其浏览器的源代码,决定从头开始重写浏览器,并把重点放在遵循W3C标准上。那时,Internet Explorer 5是W3C DOM和ECMAScript的最佳实现。第一版完整的开源Netscape代码以Mozilla的面貌于2002年问世。由此开始,浏览器领域形成了一个趋势:越来越多的浏览器开始努力遵循W3C和ECMA维护的Web标准。如今,现代浏览器(如Firefox、Mozilla、Opera、Konqueror和Safari)都严格遵循Web标准,这就使得编写跨浏览器的HTML和JavaScript等任务大大简化。Internet Explorer 6与1998年的版本5并没有太大差别,它严格禁止了最不合标准的行为。

通过prototype属性建立面向对象的JavaScript

JavaScript通过一种链接机制来支持继承,而不是通过完全面向对象语言(如Java)所支持的基于类的继承模型。每个JavaScript对象都有一个内置的属性,名为prototype。prototype属性保存着另一个JavaScript对象的引用,这个对象作为当前对象的父对象。

通过点记法引用对象的一个函数或属性时,倘若对象上没有这个函数或属性,只有此时才会使用对象的prototype属性。出现这种情况时,就会检查对象prototype属性所引用的对象,查看是否有所请求的属性或函数。如果prototype属性引用的对象也没有所需的函数或属性,则会进一步检查这个prototype对象(prototype属性引用的对象)本身的prototype属性,依次沿着链向上查找,直到找到所请求的函数或属性,或者到达链尾,如果已经到达链尾还没有找到,则返回undefined。从这个意义上讲,这种继承结构更应是一种“has a”关系,而不是“is a”关系。

如果你习惯于基于类的继承机制,可能要花一些时间来熟悉这种prototype机制。prototype机制是动态的,可以根据需要在运行时配置,而无需重新编译。可以只在需要时才向对象增加属性和函数,而且能动态地把单独的函数合并在一起,来创建动态、全能的对象。对于prototype机制的这种高度动态性,可谓褒贬不一,因为这种机制学习和应用起来很不容易,但是一旦正确地加以应用,这种机制则相当强大而且非常健壮。

这种动态性与基于类的继承机制中的多态概念异曲同工。两个对象可以有相同的属性和函数,但是函数方法(实现)可以完全不同,而且属性可以有完全不同的数据类型。这种多态性使得JavaScript对象能够由其他脚本和函数以统一的方式处理。

5-15显示了实际的prototype继承机制。这个脚本定义了3类对象:Vehicle、SportsCar和CementTruck。Vehicle是基类,另外两个类由此继承。Vehicle定义了两个属性:wheelCount和curbWeightInPounds,分别表示Vehicle的车轮数和总吨位。JavaScript不支持抽象类的概念(抽象类不能实例化,只能由其他类扩展),因此,对于Vehicle基类,wheelCount默认为4,curbWeightInPounds默认为3,000。

图见P149-1

5-15 VehicleSportsCarCementTruck对象之间的关系

 

要注意这个UML图展示了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

/* Constructor function for the Vehicle object */

function Vehicle() { }

/* Standard properties of a Vehicle */

Vehicle.prototype.wheelCount = 4;

Vehicle.prototype.curbWeightInPounds = 4000;

/* Function for refueling a Vehicle */

Vehicle.prototype.refuel = function() {

return "Refueling Vehicle with regular 87 octane gasoline";

}

/* Function for performing the main tasks of a Vehicle */

Vehicle.prototype.mainTasks = function() {

return "Driving to work, school, and the grocery store";

}

/* Constructor function for the SportsCar object */

function SportsCar() { }

/* SportsCar extends Vehicle */

SportsCar.prototype = new Vehicle();

/* SportsCar is lighter than Vehicle */

SportsCar.prototype.curbWeightInPounds = 3000;

/* SportsCar requires premium fuel */

SportsCar.prototype.refuel = function() {

return "Refueling SportsCar with premium 94 octane gasoline";

}

/* Function for performing the main tasks of a SportsCar */

SportsCar.prototype.mainTasks = function() {

return "Spirited driving, looking good, driving to the beach";

}

/* Constructor function for the CementTruck object */

function CementTruck() { }

/* CementTruck extends Vehicle */

CementTruck.prototype = new Vehicle();

 

/* CementTruck has 10 wheels and weighs 12,000 pounds*/

CementTruck.prototype.wheelCount = 10;

CementTruck.prototype.curbWeightInPounds = 12000;

/* CementTruck refuels with diesel fuel */

CementTruck.prototype.refuel = function() {

return "Refueling CementTruck with diesel fuel";

}

/* Function for performing the main tasks of a SportsCar */

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 οnclick="createVehicle();">Create an instance of Vehicle</button>

<br/><br/>

<button οnclick="createSportsCar();">Create an instance of SportsCar</button>

<br/><br/>

<button οnclick="createCementTruck();">Create an instance of CementTruck</button>

</body>

</html>

 

分别创建三种对象,并用describe函数描述,结果如图5-16所示。

 

图见P152-1

5-16 创建VehicleSportsCarCementTruck 对象并使用describe函数分别描述的结果

 

 

私有属性和使用JavaScript的信息隐藏

铁杆的面向对象设计支持者会注意到,使用prototype方法向JavaScript对象增加属性和函数时,所增加的属性和函数都是公共的,所有其他对象都能访问。对于函数来说,这通常没什么问题,因为大多数函数都确实应当提供给外部客户。但是对于属性,面向对象设计的支持者就会指出,公共属性违反了信息隐藏的概念。面向对象设计称,对象的属性应当是私有的,因为外部客户不能直接访问。外部客户只能通过公共可用的函数来访问对象的私有属性。

对于JavaScript,同样有可能创建外部客户不能访问的私有属性,而只能通过对象的(公共)方法来访问,但这一点很少有人知道。Douglas Crockford[3]提出了一种在JavaScript中创建私有属性的方法。这种方法非常简单,总结如下:

l         私有属性可以在构造函数中使用var关键字定义。

l         私有属性只能由特权函数(privileged functions)公共访问。特权函数就是在构造函数中使用this关键字定义的函数。外部客户可以访问特权函数,而且特权函数可以访问对象的私有属性。

下面来考虑前一个例子中的Vehicle类。假设你想让wheelCount和curbWeightInPounds属性是私有的,只能通过公共方法访问。新的Vehicle对象则如代码清单5-4所示。

代码清单5-4 重写后的Vehicle对象

function Vehicle() {

var wheelCount = 4;

var curbWeightInPounds = 4000;

this.getWheelCount = function() {

return wheelCount;

}

this.setWheelCount = function(count) {

wheelCount = count;

}

this.getCurbWeightInPounds = function() {

return curbWeightInPounds;

}

this.setCurbWeightInPounds = function(weight) {

curbWeightInPounds = weight;

}

 

this.refuel = function() {

return "Refueling Vehicle with regular 87 octane gasoline";

}

this.mainTasks = function() {

return "Driving to work, school, and the grocery store";

}

}

注意,wheelCount和curbWeightInPounds属性都在构造函数中使用var关键字定义,这就使得这两个属性是私有属性。属性不再是公共的,如果想通过点记法访问wheelCount属性的值,如下:

var numberOfWheels = vehicle.wheelCount;

就会返回undefined,而不是wheelCount实际的值。

由于属性现在是私有的,因此需要提供能访问这些属性的公共函数。getWheelCount、setWheelCount、getCurbWeightInPounds和setCurbWeightInPounds函数就是作此使用。现在Vehicle对象可以保证只能通过公共函数访问私有属性,因此可以满足信息隐藏的概念。

 

JavaScript中基于类的继承

JavaScript中基于prototype的继承机制可以很好地工作,但是有些人已经习惯于C++和Java等语言中基于类的继承机制,对于这些人来说,JavaScript的prototype继承机制不是一种自然的编程方法。如果你不想用基于prototype的继承,而想用一种基于类的继承方法,那就继续读下去吧。

Netscape的Bob Clary[4]也提出了一个方法,通过这种方法,一个对象可以使用一个通用的脚本从另一个对象继承属性和函数。这个脚本只是将“父”对象的属性和函数复制到“子”对象。为此,我们将说明如何对脚本稍加修改,从而只是将子对象中不存在的属性和函数复制到子对象;这样一来,子对象中的函数就能覆盖父对象的函数。在两个对象之间创建继承关系的通用函数如下:

function createInheritance(parent, child) {

var property;

for(property in parent) {

if(!child[property]) {

child[property] = parent[property];

}

}

}

createInheritance函数有两个参数,父对象和子对象。这个函数只是迭代处理父对象的所有成员(成员就是属性或函数),如果某个成员在子对象中不存在,则复制到子对象。

使用createInheritance函数相当简单:首先创建子对象的一个实例,然后使用createInheritance函数,为它传递子对象以及父对象的一个实例,如下:

var child = new Child();

createInheritance(new Parent(), child);

父对象中有而子对象中没有的所有属性和方法将复制到子对象。

汇合

前面已经了解到,JavaScript中也可以实现私有属性,而且JavaScript也能像C++和Java一样支持基于类的继承方法。为了展示这些是怎样实现的,下面说明如何转换前面使用Vehicle、SportsCar和CementTruck对象的例子,从而使用信息隐藏和继承的新模式。代码清单5-5列出了新的对象定义。

代码清单5-5 classicalInheritance.js

function Vehicle() {

var wheelCount = 4;

var curbWeightInPounds = 4000;

this.getWheelCount = function() {

return wheelCount;

}

this.setWheelCount = function(count) {

wheelCount = count;

}

this.getCurbWeightInPounds = function() {

return curbWeightInPounds;

}

this.setCurbWeightInPounds = function(weight) {

curbWeightInPounds = weight;

}

this.refuel = function() {

return "Refueling Vehicle with regular 87 octane gasoline";

}

this.mainTasks = function() {

return "Driving to work, school, and the grocery store";

}

}

function SportsCar() {

this.refuel = function() {

return "Refueling SportsCar with premium 94 octane gasoline";

}

this.mainTasks = function() {

return "Spirited driving, looking good, driving to the beach";

}

}

function CementTruck() {

this.refuel = function() {

return "Refueling CementTruck with diesel fuel";

}

this.mainTasks = function() {

return "Arrive at construction site, extend boom, deliver cement";

}

}

需要注意,SportsCar和CementTruck对象没有定义自己的wheelCount和curbWeightInPounds属性,也没有相关的存取函数,因为这些属性和函数会从Vehicle对象继承。

与前面一样,需要一个简单的HTML页面来测试这些新对象。代码清单5-6列出了测试这些新对象的HTML页面。要特别注意createInheritance函数,看看如何使用这个函数在Vehicle和SportsCar对象之间以及Vehicle和CementTruck对象之间创建继承关系。还要注意describe函数有所修改,试图直接访问wheelCount和curbWeightInPounds属性。这样做会返回一个undefined值。

代码清单5-6 classicalInheritance.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>Classical Inheritance in JavaScript</title>

<script type="text/javascript" src="classicalInheritance.js"></script>

<script type="text/javaScript">

function createInheritance(parent, child) {

var property;

for(property in parent) {

if(!child[property]) {

child[property] = parent[property];

}

}

}

function describe(vehicle) {

var description = "";

description = description + "Number of wheels (via property): "

+ vehicle.wheelCount;

description = description + "/n/nNumber of wheels (via accessor): "

+ vehicle.getWheelCount();

description = description + "/n/nCurb Weight (via property): "

+ vehicle.curbWeightInPounds;

description = description + "/n/nCurb Weight (via accessor): "

+ vehicle.getCurbWeightInPounds();

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();

createInheritance(new Vehicle(), sportsCar);

sportsCar.setCurbWeightInPounds(3000);

describe(sportsCar);

}

function createCementTruck() {

var cementTruck = new CementTruck();

createInheritance(new Vehicle(), cementTruck);

cementTruck.setWheelCount(10);

cementTruck.setCurbWeightInPounds(10000);

describe(cementTruck);

}

</script>

</head>

<body>

<h1>Examples of Classical Inheritance in JavaScript</h1>

<br/><br/>

<button οnclick="createVehicle();">Create an instance of Vehicle</button>

<br/><br/>

<button οnclick="createSportsCar();">Create an instance of SportsCar</button>

<br/><br/>

<button οnclick="createCementTruck();">Create an instance of CementTruck</button>

</body>

</html>

 

分别点击页面上的各个按钮会得到图5-17所示的结果。正如所料,试图直接访问私有属性就会返回undefined。

图见P158-1

5-17  创建Vehicle、SportsCar和CementTruck对象,并使用describe函数描述的结果。私有属性不能直接访问,见警告框中的undefined值。

小结

在这一章中,我们介绍了一些工具和技术,采用这些工具和技术,会让你的开发过程更加愉快。JSDoc可以帮助建立JavaScript代码的文档,从而使其他开发人员能够更容易地理解和使用。如果你开始经常使用Ajax技术,肯定会编写你自己的一些可重用的JavaScript库,而且会用JSDoc为代码建立文档,以便其他人更轻松地使用这些库。

HTML Validator和Checky等工具可以帮助确保你所写的HTML代码是合法的HTML。不合法的HTML会导致未预见的行为,所以使用合法的HTML或XHTML能消除可能导致错误的一些因素。另外,如果XHTML或HTML是合法的,更有可能在多个浏览器平台上有相同的表现。

Firefox和其他Mozilla浏览器中打包提供了DOM Inspector工具,利用这个工具可以将HTML文档作为一个结构化树来检查其节点。DOM Inspector允许你查看每个节点及其属性值,甚至可以动态修改属性值。可以动态地将节点从页面中的一个位置移到另一个位置,而不必重写HTML。如果要检查通过JavaScript动态创建的节点,DOM Inspector就很有用。

JSLint是一个JavaScript验证工具。尽管它不能确定JavaScript的逻辑是否正确,但确实能帮助找出语言语法中存在的错误,还能发现由于编码风格不好而可能出错的部分。

删除JavaScript中的注释行和回车换行符可以大大缩小JavaScript文件的大小,相应地,将JavaScript 文件下载到客户浏览器的时间也会减少。MemTronic的HTML/JavaScript Cruncher-Compressor不仅能删除注释行和回车换行符,还能真正压缩JavaScript代码,从而能更快地下载。压缩还有一个很好的副作用,能使JavaScript更难读,从而有助于保护JavaScript代码的内部工作不外泄,不会被别人窃取。

Firefox的Web开发扩展包为Web开发人员提供了许多有用的工具。利用这些工具,可以调整图片的大小,动态地编辑CSS样式规则,将表单方法从GET改为POST(或反之),除此以外还有很多。

最后,我们介绍了一些高级的JavaScript技术,如面向对象编程。首先,你了解了JavaScript如何使用一种基于prototype的机制来模拟继承。然后了解了JavaScript如何支持信息隐藏的概念,为此要使用只能通过公共方法访问的私有属性。最后,我们介绍了一种技术,利用这种技术,JavaScript可以模拟基于类的继承机制,这类似于C++和Java中使用的继承机制。对于那些习惯于完全面向对象语言的人来说,这种技术是一种更自然的编码风格。

作为Ajax开发人员,通过采用这些工具和技术,你会更为轻松,更加愉快。可以把这些工具都拿来试试,这样才能挑出你喜欢的工具;另外,在Web上可能还会碰到其他有用的工具。

 

 

 

 



[1] https://addons.mozilla.org/extensions/moreinfo.php?application=firefox&category=Developer %20Tools&numpg=10&id=249

[2] https://addons.mozilla.org/extensions/moreinfo.php?application=firefox&category=

Developer%20Tools&numpg=10&id=165

 

[3] http://www.crockford.com/。

[4] http://devedge-temp.mozilla.org/toolbox/examples/2003/inheritFrom/index_en.html

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值