调试一个别人的 js 程序,代码不长,不到 100 行。功能是轮显图片,4张缩略图,一个放大的图片,程序自动在小图片中循环显示,鼠标移动到缩略图上,显示相应的大图。
页面中还有其他的轮显,鼠标经过标题,改变内容的区域。
Bug 现象:鼠标经过其他轮显的区域,或者页面加载过一段时间之后,就会提示 js 有错误。
打开调试,错误内容:“SCRIPT5007: 无法设置未定义或 null 引用的属性“className” foucs.js, 行13 字符9”
相应的 js 代码如下:
var foucsbox = function (time) {
var time = time || 3500
, $ = function (id) { return document.getElementById(id); }
, topCon = $('divimgplay')
, big = $('divimginfog_imgPlayer')
, samll = $('divpageinfog_imgPlayer')
, tip = $('ptitleg_imgPlayer')
, bigimgs = big.getElementsByTagName('li')
, samllimgs = samll.getElementsByTagName('li')
, imglink = tip.getElementsByTagName('p')[0]
, slide = function (z) {
samllimgs[lastIndex].className = '';
samllimgs[z].className = 'current';
bigimgs[lastIndex].style.display = 'none';
bigimgs[z].style.display = 'block';
try {
imglink.innerHTML = samllimgs[z].getElementsByTagName('img')[0].alt;
}
catch (e) {
imglink.innerText = samllimgs[z].firstChild.firstChild.alt;
}
lastIndex = i = z;
}
, helper = function (z) {
return function (e) {
var na;
if (!e) {
e = window.event;
na = e.srcElement.nodeName;
}
else {
na = e.target.nodeName;
}
if (na === 'IMG') {
slide(z);
}
}
}
, lastIndex = i = 0, x, y = bigimgs.length
, getPrevI = function (q) { return i - q < 0 ? y - q : i - 1; }
, getNextI = function (q) { return i + q >= y ? i + q - y : i + 1; }
var s = setInterval(function () {
slide(i);
i = getNextI(1);
}, time);
try {
imglink.innerText = samllimgs[0].getElementsByTagName('img')[0].alt;
}
catch (e) {
imglink.innerText = samllimgs[0].firstChild.firstChild.alt;
}
for (x = 1; x < y; x += 1) {
bigimgs[x].style.display = 'none';
}
for (x = 0; x < y; x += 1) {
samllimgs[x].onmouseover = helper(x);
}
topCon.children[2].onclick = function (e) {
i = lastIndex;
var t;
if (!e) {
e = window.event;
t = e.srcElement;
} else {
t = e.target;
}
switch (t.className) {
case 'icon_prev':
slide(getPrevI(1));
break;
case 'icon_next':
slide(getNextI(1));
break;
}
};
topCon.onmouseover = function () {
clearInterval(s);
};
topCon.onmouseout = function () {
s = setInterval(function () {
slide(i);
i = getNextI(1);
}, time);
};
};
问题出现在引用了超界的数组成员的一个属性。
跟踪代码,发现该数组应拥有的元素应该是4个,但是超界时给出的下标却是7或者9。
找到获取上一个、下一个下标的代码:
getNextI = function (q) { return i + q >= y ? i + q - y: i + 1; }
对越界也有判断,而且跟踪了很多次,返回值一直在 0 到 3,其他的地方也没有改变,可是在页面里面运行一段时间,又会出现那个问题。
结合出现 bug 的现象,鼠标移过其他的轮显区域就会报这个错误,开始怀疑是不是有的变量作用域不对,被函数外面的变量值给干扰。
而且这个变量很有可能出现在当做下标的循环变量中。
逐步排查,发现在神明变量时,有这样一句:
lastIndex = i = 0, x, y = bigimgs.length
lastIndex,x,y 都没有问题,但是这里的 i 并不是申明变量,而是直接引用了。
js 引擎会在当前的作用域内找 i 这个变量的申明,如果没有找到,会默认在上一级的作用域内搜寻,直到搜寻全局变量,如果全局变量里面也没有 i ,那么就会将这个 i 作为全局变量来处理。
如果恰巧在其他的代码中也引用了这个全局变量 i ,对 i 的值做了操作,那么灾难就来了,就会出现上面的问题。
将上面申明变量的语句改为
lastIndex = 0,
i = 0,
x = 0,
y = bigimgs.length,
将 i 也定义为函数内部的变量,不受外部程序的影响。就此不再报错,bug 消除。
总结:
可见变量的作用域是何等的重要,特别是类似于 i, j 之类的,会经常用到的循环变量,很容易搞串,而循环变量在程序逻辑中又是非常重要的,所以在程序开始时,一定要做好变量的申明。
在找这个 bug 时,一开始也走了不少弯路,因为总是在鼠标经过其他的轮显块的时候出现这个 bug ,所以以为造成 bug 的原因是其他的轮显块造成的,再加上没有代码注解,分析代码的逻辑都用了很长的时间,实际上这个 bug 不用太了解代码的逻辑,只要能够准确定位 bug 出现的位置,就能够找到 bug ,并消除。