JavaScript DOM 编程艺术 Note(1)


我记得以前曾经在哪儿看过一句话

“一个程序员写代码的时候,只有一小部分的代码是用来实现功能的,其余大部分代码都是在处理特殊情况”

当然 我觉得说得有些夸张了233 不过不能否认的是 代码的健壮性是衡量一段代码是否良好的重要指标之一

最近刚开始学JavaScript 觉得书里一些方面说得挺有道理 故而做笔记加以记录。

与DOM脚本编程工作相关的几个良好习惯

  • 平稳退化
  • 分离JavaScript
  • 向后兼容性
  • 性能考虑

平稳退化

平稳退化 (graceful degradation):意指在用户的浏览器不支持JavaScript或用户手动禁用了JavaScript的情况下,仍能保证他们能够顺利浏览网站(即便一些进阶的功能无法使用)

比如,当你需要实现一个以新窗口的形式打开一个链接的功能(必要),你或许会选择写一个JavaScript函数并存入外部文件,并在网页中通过<script>标签导入。那么,如何调用函数呢?

不推荐的做法

  • 使用伪协议
  • 用内嵌事件处理函数

伪协议 (pseudo-protocol) , 即类似于(假设popUp()即所写的函数)

1
<a href="javascript:popUp('http://www.example.com/');">Example</a>

这种语句在支持“javascript:”伪协议的浏览器中运行正常,较老的浏览器则会尝试打开那个链接而失败,支持这种伪协议但禁用JavaScript功能的浏览器会什么也不做。

内嵌事件 :如把onclick事件处理函数作为属性嵌入<a>标签,

1
2
<a href="#" onclick="popUp('http://www.example.com/');
return false;">Example</a>

同上,对于不支持或禁用JavaScript的用户来说依旧不能实现功能。

推荐的做法
增加一个选择:使用内嵌的事件处理函数退而求其次地实现最基本的功能。

依旧以这个比方为例,我们可以把href属性设置为真实有效的URL地址。

1
2
<a href="http://www.example.com/"
onclick="popUp(this.href); return false;">Example</a>

虽然不能实现打开一个新窗口的进阶功能,但基本功能——打开这个链接能够实现,并没有彻底失效。
这是一个经典的“平稳退化”的例子。


分离JavaScript

为了过渡到分离JavaScript的话题,先提及一个概念——渐进增强

所谓“渐进增强”就是用一些额外的信息层去包裹原始数据。按照“渐进增强”原则创建出来的网页几乎都符合“平稳退化”的原则。

类似于CSS,把JavaScript代码从HTML文档里分离出来可以让JavaScript工作得更好。

JavaScript语言不要求事件必须在HTML文档里处理,因而我们可以在外部JavaScript文件里把一个事件添加到HTML的文档中的某个元素上:
element.event = action…

而确定事件的元素,可以用class或id属性来解决(配合getElementById)。

若涉及多个元素,则可以用getElementsByTagName或getAttribute

依旧使用上面的例子。

您只需要在HTML文档里输入:

1
<a href="http://www.example.com/" class="popup">Example</a>

并在JavaScript文档里输入:

1
2
3
4
5
6
7
8
9
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++){
if(links[i].getAttribute("class") == "popup"){
links[i].onclick = function(){
popUp(this.getAttribute("href"));
return false;
}
}
}

此时,已经成功分离JavaScript。

然而,还有一个问题尚待解决:若直接将上面的代码写入JavaScript文档,是无法正常运行的。因为这段代码的第一行会在JavaScript文档被加载时立刻执行。若JavaScript文档是从HTML文档的<head>部分由<script>标签调用的,它将在HTML文档之前加载到浏览器里。同样,如果<script>标签位于文档底部之前,就不能保证哪个文件最先结束加载。因为脚本加载时文档可能不完整,所以模型也不完整。没有完整的DOM,getElementByTagName等方法就不能正常工作。

而由于onload方法的存在,使上段代码的正常运行成为了可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
window.onload = prepareLinks;
function prepareLinks(){
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++){
if(links[i].getAttribute("class") == "popup"){
links[i].onclick = function(){
popUp(this.getAttribute("href"));
return false;
}
}
}
}
...
function popUp(winURL){
...
}

向后兼容

不得不说,即使你不愿意相信,确实存在那些至此JavaScript但无法理解DOM提供的方法和属性的浏览器。

对这个问题最简单的解决方案是:对象检测 (object detection)

如,您使用了getElementById()方法,即可在调用这个方法前检查用户使用的浏览器是否支持这个方法。

依旧以上段代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
window.onload = function(){
if(!document.getElementsByTagName) return false;
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++){
if(links[i].getAttribute("class") == "popup"){
links[i].onclick = function(){
popUp(this.getAttribute("href"));
return false;
}
}
}
}
...
function popUp(winURL){
...
}

注意:在使用if()条件句进行对象检测时,一定要删掉方法名后面的圆括号。若不删掉,测试的将是方法的结果,无论方法是否存在。


性能考虑

对于性能优化,以下讨论几个方面:

  • 尽量少访问DOM和尽量减少标记
  • 合并和放置脚本
  • 压缩脚本

尽量少访问DOM和尽量减少标记

只要是查询DOM中的某些元素,浏览器都会搜索整个DOM树,从中查找可能匹配的元素。若代码中多次使用相关方法进行相同操作,无疑是在浪费搜索次数。

更好的办法是将第一次搜索的结果存在一个变量中,以待第二次对该结果实现其他功能。

当然,或许还存在多个函数重复做一件事的情况。比如,一个函数检查每个链接中的A类,另一个函数检查每个链接中的B类,同样也会造成搜索浪费。在多个函数都会取得一组类似元素的情况下,建议考虑重构代码,将搜索结果存在一个全员变量里,或把一组元素直接以参数形式传递给函数。

另一个需要注意的地方:尽量减少文档中的标记数量。过多不必要的元素会增加DOM树的规模,进而增加遍历时间。

合并和放置脚本

推荐将实现不同功能的函数合并到较少个脚本文件中。这样即可减少加载页面时发送的请求数量。

同样,脚本在标记中的位置对页面的初次加载也有很大影响。

一般来说,根据HTTP规范,浏览器每次从同一个域名中最多只能同时下载两个文件。而下载脚本期间,浏览器不会下载其他任何文件,即使是来自不同域名的文件也不会下载,所有其他资源都要等脚本加载完毕后才能下载。

故将脚本放在文档的<head>区域是存在问题的。它会导致浏览器无法并行加载其他文件。
建议将<script>标签都放在文档末尾,即</body>之前,就可以使页面变得更快。

压缩脚本

所谓压缩脚本,指的是把脚本文件中不必要的字节,如空格和注释等,统统删除。

诚然,压缩后的脚本不便于阅读,却可以大幅度减少文件大小。多数情况下,您应该有两个版本,一个是工作副本,可以修改代码并添加注释;另一个是精简副本,用于放在站点上。通常,为了与非精简版本区分开,最好在精简副本的文件名中加上min字样。

1
<script src="scripts/scriptName.min.js"></script>

推荐几个有代表性的代码压缩工具:

(P.S. 在md文件中如果要写纯文本的”<”和”&”的话,”<”要写成”&lt ;”(去掉分号前面的空格) ,”&”要写成”&amp ;”(去掉分号前面的空格) ,即使用实体的形式)

__END__