『犀牛书』读书笔记之脚本化图片

《JavaScript权威指南》一书俗称『犀牛书』,是一本厚厚的经典巨著,堪称JavaScript界的『圣经』。第一次读这本书由于懒怠,并没有读得很深入,这次由于项目需要,我对书中关于图片编程的部分又进行了研读,并整理成本篇读书笔记。

替换图片

Web页面使用HTML的<img>元素来嵌入图片,可以设置元素的src属性,将其指向一个新的URL使浏览器载入并展示一张新的图片。

这样可以实现在HTML文档中动态替换图片,比如下面一个实现图片翻转简单的例子:

<img src="avatar.png" onmouseover="this.src='avatar_rollover.png'" onmouseout="this.src='avatar.png'">

当鼠标指针经过或离开<img>元素时,事件处理程序会重新设置其src属性。

预加载

为了实现较高的响应度,需要想办法来确保图片的预加载,让浏览器缓存起来。为了强制让图片缓存起来,可以先用Image()构造函数来创建一个屏幕外图像对象,然后,对其src属性设置成我们期望的URL。
因为图片元素并没有添加到文档中,所以,它不可见,但是浏览器还是会加载图片并将其缓存。之后,当设置成相同的URL来显示图片时,它会很快从浏览器换从中加载。于是,上面的代码可以修改成下面这样:

<script>
(new Image()).src="avatar_rollover.png";
</script>

<img src="avatar.png" onmouseover="this.src='avatar_rollover.png'" onmouseout="this.src='avatar.png'">

优雅化

那我们想在任意的<img>元素上,只要简单指定一个属性(比如data-rollover),就会创建一个图片翻转的效果,可以怎么做呢?看下面一段代码:

<img src="avatar.png" data-rollover="avatar_rollover.png">

<script type="text/javascript">
// 注册函数f,当文档载入完成时执行函数f
function onLoad(f) {
if (onLoad.loaded) //如果文档已经载入完成
window.setTimeout(f,0); //将f放入异步队列,并尽快执行它
else if (window.addEventListener) //注册事件
window.addEventListener("load",f,false);
}

// 给onload设置一个标志,用来指示文档时候加载完成
onLoad.loaded = false;
// 注册一个函数,当文档载入完成时设置这个标志
onLoad(function() {
onLoad.loaded = true;
});

onLoad(function() {
// 遍历所有图片,查找data-rollover属性
for (var i = 0; i < document.images.length; i++) {
var img = document.images[i];
var rollover = img.getAttribute("data-rollover");

if (!rollover) continue; // 跳过没有data-rollover属性的图片

// 将翻转的图片缓存
(new Image()).src = rollover;

// 定义一个属性来标识原图片的URL
img.setAttribute("data-rollout", img.src);

// 注册事件处理函数来实现翻转效果
img.onmouseover = function() {
this.src = this.getAttribute("data-rollover");
};
img.onmouseout = function() {
this.src = this.getAttribute("data-rollout");
};
}
});
</script>

这样代码是不是一下子高大上了,确实『优雅』了很多。

堆栈溢出

我在练习的时候,无意中将指示文档加载完成标志的那部分代码写在function onLoad(f) { ... }里面了,如下:

function onLoad(f) {
if (onLoad.loaded)
window.setTimeout(f,0);
else if (window.addEventListener)
window.addEventListener("load",f,false);

onLoad.loaded = false;

onLoad(function() {
onLoad.loaded = true;
});
}

然后,Chrome就报错了:

Uncaught RangeError: Maximum call stack size exceeded

这个错误是说最大调用堆栈大小超出,后来在Stack Overflow上找到了这样一段解释:

@alex:

It means that somewhere in your code, you are calling a function which in turn calls another function and so forth, until you hit the call stack limit.
This is almost always because of a recursive function with a base case that isn’t being met.
In order to fix it, ensure that your recursive function has a base case which is able to be met.

这也就不难理解,为什么会堆栈溢出了。

当然,代码也可以写成window.onload = function() { ... }这样的形式,既简洁不少,又避开了上述问题:

window.onload = function() {
for (var i = 0; i < document.images.length; i++) {
var img = document.images[i];
var rollover = img.getAttribute("data-rollover");

if (!rollover) continue;

(new Image()).src = rollover;
img.setAttribute("data-rollout", img.src);

img.onmouseover = function() {
this.src = this.getAttribute("data-rollover");
};
img.onmouseout = function() {
this.src = this.getAttribute("data-rollout");
};
}
};