第五章 平稳退化
主要目的,让那些不支持或者禁用了javascript功能的浏览器也能顺利访问网站。
5.1弹出窗口
JS使用window对象的open()
方法创建新的浏览器窗口,语法:
window.open(url,name,features)
三个参数是可选的。
- 参数1是想在新窗口中打开的网页的URL地址。若省略这个参数,屏幕上将弹出一个空白的浏览器窗口。
- 参数2是新窗口的名字,可在代码里通过这个名字与新窗口进行通信
- 参数3是一个以逗号分隔的字符串,其内容是新窗口的各种属性(窗口尺寸等)。需注意:新窗口的浏览功能要少而精。
open()
方法是使用BOM的一个很好的案例,它的功能对文档的内容也无任何影响,至于浏览环境有关。如下实例:
functio popUp(winURL){
window.open(winURL,"popup","width=320,height=480");
}
这个函数经打开一个宽为320,高为480的新窗口“popup”。
5.2“JavaScript:”伪协议
真协议用来在因特网上的计算机之间传输数据包,如HTTP协议(http://)、FTP协议(ftp://),伪协议则是一种非标准化的协议。“javaScript:”让我们通过一个链接来调用JavaScript函数。
<a href="javascript:popup('http://www.ecample.com/');">Example</a>
在支持“javascript:”伪协议的浏览器中运行正常,较老浏览器会尝试打开那些链接但失败,支持这种伪协议但禁用了JavaScript功能的浏览器则什么都不做。
总之,在文档里通过这种伪协议调用JavaScript代码的做法非常不好。
5.3 向CSS学习
作为CSS技术的突出优点,文档结构与文档样式的分离可以确保网页都能平稳退化,具备CSS支持的浏览器可以呈现美轮美奂的网页,不支持或禁用了CSS的浏览器同样可以把网页内容按照正确的结构显示出来。
5.3.1 渐进增强
所谓的渐进增强就是用些额外的信息层取包裹原始数据。类似于CSS、JS和DOM提供的所有功能也应该构成一个额外的指令层。CSS负责提供关于“表示”的信息,JS负责提供关于“行为”的信息。行为层的应用方式与表示层类似。
CSS与HTML的分离可以让CSS工作的更好,同样适用于JS行为层。
5.4 分离JavaScript
虽然将JS与HTML分的很开了,实际负责完成各项任务的JS函数都已存入外部文件中,但是还有 内嵌的事件处理函数可以进一步分离。
类似于使用style属性,HTML文档里的onclick属性也是一种既没效率又易引发问题的做法。如果我们用CSS机制中的class或id属性那样,把JS代码调用行为与HTML文档结构和内容分离,网页就会健壮得多。
把一个事件添加到HTML文档中的某个元素上:
getElementById("id").event = action...
实例:
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;
}
}
}
如上实例在没有完整的DOM情况下,getElementByTagName等方法不能够正常工作。
必须让这些代码在HTML文档全部加载到浏览器之后马上开始运行。还好,文档将被加载到一个浏览器窗口里,document对象又是Window对象的一个属性。当window对象触发onload事件时,document对象已经存在。
我将把我的JS代码打包在prepareLinks函数里,并把这个函数添加到window对象的onload事件上去。这样一来,DOM就可以正常工作了。
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;
}
}
}
}
5.5 向后兼容
5.5.1 对象检测
几乎所有东西(包括方法在内)都可以被当作对象,意味着我们可以吧不支持某个特定DOM方法的浏览器检测出来:
if(method){statements}
function myFunction(){
if (document.getElementsByTagName) {
statements using getElementsByTagName
};
}
需注意的是,if里的方法不要加(),否则判断的是方法的结果,无论方法是否存在。
同样,如果存在多个检测,如上的代码就会显得很冗长,我们需要把测试条件从“如果你理解…”改为“如果你不理解….”,需要使用“逻辑非”操作符,即if(!method)
,用来测试getElementById是否存在的语句可以改为如下:
if(!getElementById) return false;
这就类似于说,如果你不是别getElementById,那就直接退出吧。
若有多个测试存在,只需加上“逻辑或”操作符将其合并,如下:if(!getElementById || !getElementsByTagName) return false;
虽然只是一条简单的if语句,但它可以确保那些古老的浏览器不会因为我的脚本代码而出现问题。这样实现了脚本良好的向后兼容性。因为我在给网页添加各种行为时始终遵循“渐进增强”的原则,所以确定我添加的那些都能平稳退化,我的网页在那些“古老的”浏览器里也能正常浏览,在那些只支持部分JavaScript功能但不支持DOM的浏览器仍可访问我网页的内容。
5.6 性能考虑
5.6.1 尽量少访问DOM和尽量减少标记
访问DOM的方式对脚本性能会产生非常大的影响。以下为例:
if (document.getElementsByTagName("a").length>0) {
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
links[i]
//对每个链接做点处理
};
};
虽然这段脚本可以正常运行,但是仔细看下,居然两次使用了getElementsByTagName这个方法去搜索同一个标签,真是一种搜索浪费。不管什么时候,只要是查询DOM树中的某些元素,浏览器便会搜索整个DOM树。更好的办法是将第一次搜索的结果存储在一个变量中,以后重复使用该变量。改良后的代码如下:
var links = document.getElementsByTagName("a");
if (links.length>0) {
for (var i = 0; i < links.length; i++) {
links[i]
//对每个链接做点处理
};
};
这样一来,代码功能没变,但搜索DOM的次数有两次降低到了一次。
如果有多个函数重复做同一件事,可能不太好发现。比如,一个函数检查每个连接中的popup类,另一个函数检查每个连接中的hover类,那么同样也会造成搜索浪费。在多个函数都会取得一组类似元素的情况下,可考虑重构代码。把搜索结果保存在一个全局变量里,或者把一组元素直接以参数形式传递给函数。
另外一个需注意,尽量减少文档中的标记数量。过多不必要的元素只会增加DOM树的规模,进而增加遍历DOM树以查找特定元素的时间。
5.6.2 合并和放置脚本
常见的脚本引用,如下的这种情况,最好也不要出现:
<script src="script/functionA.js"></script>
<script src="script/functionB.js"></script>
<script src="script/functionC.js"></script>
<script src="script/functionD.js"></script>
推荐做法是将functionA、functionB、functionC、functionD都合并到一个脚本文件中,这样可以减少加载页面时发送的请求数量。减少请求数量通常是性能优化首先考虑项。
同样的,根据渐进增强的原理,应该把<script>
标签放在</body>
之前,让页面变得更快。
5.6.3 压缩脚本
除了以上做法,还有一样操作也可用来加快网页加载速度:压缩脚本。
所谓压缩,指的是把脚本文件中不必要的字节,如空格、注释、统统删除。目前有许多工具可用来压缩脚本。
压缩精简后的代码虽不易看懂,却能大幅减少文件的大小。多数情况下,应该有两个脚本的版本。一个是工作副本,可用来修改代码并添加注释;另一个是精简副本,用于放在站点上。为了便于区分这两个版本,最好在精简版本的文件名加上min字样:
<script src="script/functionB.min.js"></script>