回顾之前的代码,即使js功能已被禁用,用户也能浏览图片库里的所有图片,网页里的所有连接也都可以正常工作:
<li>
<a href="image/btn_acomlete_1.png" onclick="showPic(this);return false;" title="A fireworks display">Firework</a>
</li>
在没有js干扰的情况下,浏览器将沿着href属性给出的链接前进,用户将看到一张新的图而不是该图片无法显示之类的出错信息。虽然用户体验比js的效果要略差一些,但网页的基本功能并未受到损害——页面上的所有内容都能访问。
如果我们选用的是js的伪协议,链接将如下所示:
<li>
<a href="javascript:showPic('image/btn_acomlete_1.png');return false;" title="A fireworks display">Firework</a>
</li>
如果我们把链接写成上面这样,他们在不支持js功能的浏览器里将毫无用处。
类似地,包这些链接写成#记号也会导致类似的问题,但令人遗憾的是,这个技巧在那些利用剪贴操作编写的js代码里相当常见。类似于js伪协议时的情况,如果当初使用的是#记号,那些没有启动js功能的用户也将无法正常浏览我的图片库:
<li>
<a href="#" onclick="showPic('image/btn_acomlete_1.png');return false;" title="A fireworks display">Firework</a>
</li>
把href属性设置为一个真实存在的值不过是举手之劳,但图片库却因此能够平稳退化。虽然没有请js功能的用户需要在浏览器里点击后退按钮才能重新看到我的图片清单,但总比根本看不到好很多。
-
它的js和html标记是分离的吗
由于我们onclick是在html中实现的,理性情况下,应该在外部文件里完成添加onclick事件处理函数的工作,这样才能让标记文档没有杂质:
把js代码移出html文档不是难事,但为了让浏览器知道里面都有哪些链接有着不一样的行为,我们必须找到一种挂钩把js代码与html文档中的有关标记关联起来。有很多种办法可以让我们达到这一目的。
可以像下面这样给图片清单里的没个链接分别添加一个如下所示的class属性:
<li>
<a href="image/btn_acomlete_1.png" class="gallerypid" title="A fireworks display">Firework</a>
</li>
但这种技术不够理想,这与给它们分别添加事件处理函数同样麻烦。
图片清单里各个链接有一个共同点:它们都包含在同一个列表清单元素里。给整个清单设置一个独一无二的ID的办法要简单很多:
<ul id="imageallery">
<li>
<a href="image/btn_acomlete_1.png" title="A fireworks display">Firework</a>
</li>
<li>
<a href="image/btn_acomlete_2.png" title="A cup of tea">tea</a>
</li>
<li>
<a href="image/btn_allcomlete_1.png" title="A red rose">Rose</a>
</li>
<li>
<a href="image/btn_allcomlete_2.png" title="A lalala">Big bar</a>
</li>
<img id="placeholder" src="image/img_receive.png" alt="my image gallery" />
<p id="description">Choose an image.</p>
</ul>
-
添加事件处理函数
现在需要编写一个简答的函数把有关操作关联到onclick事件上,我将其命名为prepareGallery。
此函数将完成以下工作:
- 检查浏览器是否理解getElementsByTagName。
- 检查浏览器是否能理解getElementById。
- 检查当前网页是否存在一个id为imagegallery的元素。
- 遍历imagegallery元素中所有连接。
- 设置onclick事件,让它在有关连接被点击时完成以下操作:
- 把这个链接作为参数传递给showPic函数;
- 取消链接被点击时的默认行为,不让浏览器打开这个链接。
首先我们先定义prepareGallery函数,不需要参数
function prepareGallery(){
}
- 检查点
首先先检查当前浏览器是否理解名为getElementsByTagName的DOM方法。
if(!document.getElementsByTagNamet) return false;
再检查浏览器是否理解getElementById
if(!document.getElementById) return false;
我们在编写这个函数将处理id等于imagegallery的那个元素所包含的链接,如果不存在,我的这个函数也就无需继续执行了。
与前面两项测试一样,我将使用逻辑非操作符来进行这一测试:
if(!document.getElementById("imageallery")) return false;
- 变量名里有什么
var gallery = document.getElementById("imageallery");
下面遍历imagegallery元素中所有链接,用getElementsByTagName获取数组
var links = gallery.getElementsByTagName("a");
- 遍历
- 改变行为
- 完成
function prepareGallery(){
if(!document.getElementsByTagNamet) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imageallery")) return false;
var gallery = document.getElementById("imageallery");
var links = gallery.getElementsByTagName("a");
for (var i = 0 ;i<links.length ; i++)
{
links[i].onclick = function(){
showPic(this);
return false;
}
}
}
-
共享onload事件
必须执行prepareGallery函数才能对onclick事假进行绑定。
如果直接执行这个函数,它将无法完成其工作。如果在html文档完成加载之前执行脚本,此时DOM是不完整的。具体到prepareGallery函数,它的第三喊代码将测试imagegallery元素是否存在,如果DOM不完整,这项测试准确性就无从说起,事态的发展就会偏离我们的计划。
应该让这个函数在网页加载完立即执行,网页加载完毕时会触发一个onload事件,这个事件与window对象相关联。为了让事态的发展不偏离计划,必须把prepareGallery函数绑定到这个事件上:
window.onload = prepareGallery;
这样虽解决了问题但不够完美。
假设有两个函数firstFunction和secondFunction。如果想让他们俩都在页面加载时得到执行,该怎么办。如果逐一绑定在onload事件上,它们当中将只有最后那个才会被执行:
window.onload = firstFunction;
window.onload = secondFunction;
secondFunction将取代firstFunction。你可能会想每个处理函数只能绑定一条指令。有一种办法可以避免这种这种难题:可以先创建一个匿名函数来容纳这两个函数,然后把那个匿名函数绑定到onload事件上:
window.onload = function(){
firstFunction();
secondFunction();
}
它确实能很好的工作——在需要绑定的函数不是很多场合,这应该是最简单的解决方案了。
这里还有一个弹性最佳的解决方案——不管你打算在页面加载完毕时执行多少个函数,他都可以应对自如。这个方案需要额外编写一些代码,但好处是一旦有了那些代码,这个函数绑定到window.onload事件就非常容易执行了。
这个函数的名字是addLoadEvent,它是由Simon Willison编写的。它有一个参数:打算在页面加载完毕时执行的函数的名字。
下面是addLoadEvent函数将要完成的操作。
- 把现有的window.onload事件处理函数的值存入变量onload。
- 如果在这个处理函数上还没有绑定任何一个函数,就像平时那样吧新函数添加给它。
- 如果在这个处理函数上已经绑定了一些函数,就把新函数追加到现有指令的末尾。
function addLoadEvent(func){
var oldonload = window.onload;
if(typeof window.onload != 'function'){
window.onload = func;
}
else
{
window.onload = function(){
oldonload();
func();
}
}
}
这将把那些页面加载完毕时执行的函数创建为一个列队。如果想把刚才那两个函数添加到这个列队里去,只需写出以下代码就行了:
addLoadEvent(firstFunction);
addLoadEvent(secondFunction);
这个函数非常实用,尤其是在代码变得越来越复杂的时候。无论打算在页面加载完毕时执行多少个函数,只要多谢一条语句就可以安排好一切。
这个解决方案对prepareGallery函数来说好像有点大材小用,因为只有这一个函数穾在页面加载完毕时执行。可是,为以后的扩展做一些准备工作不是坏事。
-
不要做太多假设
我在showPic函数里发现的第一个问题是,我们没有让他进行任何测试和检查。
showPic函数将由prepareGallery函数调用,而我们已经在开头对getElementById和getElementsByTagName等DOM方法是否存在进行过检讨,所以我确切地知道用户浏览器会不会因为解决不了这两个方法而出问题。
不过,我还是做了太多假设。别的先不说,我在代码里用到了id属性值等于palceholder和description元素,但并没有对这些元素是否存在做任何检测:
所以我们要增加一些语句来检测这些元素是否存在。
showPic函数负责完成两件事:一是找出id属性值是palceholder的图片并修改其src属性;二是找出id属性是description的元素并修改其第一个子元素的nodeValue属性。第一件事是这个函数必须完成的任务,第二件事只是一项锦上添花的补充。因此只要palceholder图片存在,即使description元素不存在,切换显示新图片的操作也将照常进行。
var placeholder = document.getElementById("placeholder");
if(!placeholder) return false;
但是如果把palceholder图片从标记文档中删掉并在浏览器里刷新这个页面,就会出现这种状况,无论点击imagegallery清单的哪一个连接都没有任何响应。
这意味着脚本不能平稳退化。此时,应该让浏览器打开那个被点击的链接,而不是让省么事情都不发生。
问题在于prepareGallery函数做出了这样一个假设:showPic函数肯定会正常返回。基于这一假设,prepareGallery函数取消了onclick事件的默认行为:
是否返回一个false值以取消onclick事假的默认行为,其实应该由showPic函数决定。showPic应返回两个坑能的值。
如果图片切换成功,返回true;
图片切换失败,返回false;
function showPic(whichpic){
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
if(placeholder)
{
placeholder.setAttribute("src",source);
var text = whichpic.getAttribute("title");//获取whichpic对象的title属性并把值存入text
var description = document.getElementById("description");
description.firstChild.nodeValue = text;
}
//alert(description.fristChild.nodeValue);
}
function prepareGallery(){
if(!document.getElementsByTagName) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imageallery")) return false;
var gallery = document.getElementById("imageallery");
var links = gallery.getElementsByTagName("a");
for (var i = 0 ;i<links.length ; i++)
{
links[i].onclick = function(){
return !showPic(this);
}
}
}
-
优化
这几个函数已经相当完善了。虽然它们的长度有所增加,但它们对标记的依赖和假设已经比原先少多了。尽管如此,在showPic函数里仍存在一些需要处理的假设。
比如说,假设每个链接都有一个title属性:
var text = whichpic.getAttribute("title");
为了检测title是否存在,这个if表达式将被求值为true。如果title属性不存在,whichpic.getAttribute("title")将等于null,而这个if表达式被求值为false。
这个if表达式还可以简写为:
if(whichpic.getAttribute("title"))
只要这个title属性存在,这个if表达式就会返回一个true值。
作为一种简单的视觉反馈,在title属性不存在时把变量text的值设置为空字符串:
if(whichpic.getAttribute("title")){
var text = whichpic.getAttribute("title");//获取whichpic对象的title属性并把值存入text
}
else
{
var text = " ";
}
如果想十全十美的话,可以对任何一种情况进行检测。比如说,检测palceholder元素是否存在,但需要假设那是一张图片。为了验证这种情况,可以用nodeName属性来增加一项测试。
if(placenolder.nodeName!= "IMG")return false;
nodeName属性总是返回一个大写字母的值,即使元素在html文档里是小写字母。
我还可以引用更多的检查。比如说,假设description元素的第一个子元素(fristChild)是一个文本节点。我应该对此进行检查。
可以利用nodeType属性来进行这项检查。
if(description.fristChild.nodeType == 3){
description.fristChild.nodeValue = text;
}
function showPic(whichpic){
if(!document.getElementById("placeholder")) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
if(placeholder.nodeName != "IMG") return false;
placeholder.setAttribute("src",source);
if(document.getElementById("placeholder"))
{
if(whichpic.getAttribute("title")){
var text = whichpic.getAttribute("title");//获取whichpic对象的title属性并把值存入text
}
else
{
var text = " ";
}
var description = document.getElementById("description");
if(description.fristChild.nodeType == 3)
{
description.firstChild.nodeValue = text;
}
}
return true;
//alert(description.fristChild.nodeValue);
}
-
键盘访问
在有关onclick事件处理的脚本里,有一项优化工作是不能不考虑的。
下面是prepareGallery()函数中的核心代码:
links[i].onclick = function(){
return !showPic(this);
}
这段代码本身并没有问题,但是用户只能用鼠标来点击这个链接。
onkeypress键盘处理事件。
如果想让onkeypress的事件与onclick事件触发的是同样的行为,可以简单地把有关指令赋值一份:
links[i].onclick = function(){
return !showPic(this);
}
links[i].onkeypress = function(){
return !showPic(this);
}
有一种更简单的方法可以确保onkeypress模仿onclick事件的行为:
links[i].onkeypress = links[i].onclick;
-
小心onkeypress
onkeypress很容易出现问题,所以我们将不使用它。用户每按下一个按键都会触发它,在某些浏览器里甚至Tab键!这意味着如果绑定在onkeypress事件上的处理函数上返回的是false,那些只使用键盘访问的用户将无法离开当前页面。
onclick事件处理函数用键盘时也会调用。
function showPic(whichpic){
if(!document.getElementById("placeholder")) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
if(placeholder.nodeName != "IMG") return false;
placeholder.setAttribute("src",source);
if(document.getElementById("placeholder"))
{
if(whichpic.getAttribute("title")){
var text = whichpic.getAttribute("title");//获取whichpic对象的title属性并把值存入text
}
else
{
var text = " ";
}
var description = document.getElementById("description");
if(description.fristChild.nodeType == 3)
{
description.firstChild.nodeValue = text;
}
}
return true;
//alert(description.fristChild.nodeValue);
}
function prepareGallery(){
if(!document.getElementsByTagName) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imageallery")) return false;
var gallery = document.getElementById("imageallery");
var links = gallery.getElementsByTagName("a");
for (var i = 0 ;i<links.length ; i++)
{
links[i].onclick = function(){
return !showPic(this);
}
}
}
-
把js和cs结合起来使用
把js代码从html文档里分离出去还带来另一个好处。把内嵌型事件处理函数移出标记文档时,我们在文档里为js代码留下了一个“挂钩”:
<ul id = "imagegallery">
这个挂钩完全可以用在css样式表里。
比如说如果不想把图片清单显示成一个带项目符号的列表,则完全可以利用这个imagegallery写出如下所示的css语句。
#imagegallery{
list-style: :none;
}