在进行JavaScript性能优化时,需要注意两件事:不要做不必要的工作,不要重复做已经完成的工作。不要做不必要的工作就要重构代码,降低冗余率。不要重复做已经完成的工作通常难以确定,因为工作可能因为各种原因而在很多地方被重复。
也许最常见的重复工作是浏览器检测。大量代码依赖于浏览器的功能。以事件句柄的添加和删除为例,典型的跨浏览器代码如下:
function addHandler(target,eventType,handler){
if(target.addEventListener){//DOM2 Events
target.addEventListener(eventType,handler,false);
}else{//IE
target.attachEvent("on"+eventType,handler);
}
}
function removeHandler(target,eventType,handler){
if(target.removeEventListener){//DOM2 Events
target.removeEventListener(eventType,handler,false);
}else{//IE
target.detachEvent("on"+eventType,handler);
}
}
在上面代码中,通过测试addEventListener和removeEventListener来检查DOM2的事件支持情况,它能够被除IE之外的所有现代浏览器所支持。如果这些方法不存在于target中,那么就认为当前浏览器是IE,并且使用IE特有的方法。
这两个函数隐藏着性能问题:每次函数调用时都执行重复工作,检查某种方法是否存在。在每次调用中重复同样的工作是一种浪费。假设target的唯一值就是DOM对象,而且用户不可能在页面加载时改变浏览器,如果在调用addHandler时首先调用了addEventListener,那么每个后续调用都要调用addEventListener。
(1)延迟加载
延迟加载就是在信息被使用之前不做任何工作。例如,在上面示例中不需要判断使用哪种方法附加或分离事件句柄,直到函数被调用。
function addHandler(target,eventType,handler){
if(target.addEventListener){//DOM2 Events
addHandler=function(target,eventType,handler){
target.addEventListener(eventType,handler,false);
};
}else{//IE
addHandler=function(target,eventType,handler){
target.attachEvent("on"+eventType,handler);
};
}
addHandler(target,eventType,handler);
}
function removeHandler(target,eventType,handler){
if(target.removeEventListener){//DOM2 Events
removeHandler=function(target,eventType,handler){
target.addEventListener(eventType,handler,false);
};
}else{//IE
removeHandler=function(target,eventType,handler){
target.detachEvent("on"+eventType,handler);
};
}
removeHandler(target,eventType,handler);
}
这两个函数在第一次被调用时检查一次并决定使用哪种方法附加或分离事件句柄,然后原始函数就被包含适当操作的新函数覆盖了,最后调用新函数并将原始参数传给它。以后再调用addHandler或removeHandler时不会再次检测,因为检测代码已经被新函数覆盖了。
调用一个延迟加载函数总是在第一次时需要较长时间,因为必须在运行检测后调用另一个函数以完成任务。后续调用同一函数将快很多,因为不再执行检测了。延迟加载适用于函数不会在页面上立即用到的场合。
(2)条件预加载
条件预加载就是在脚本加载之前提前进行检查,而不用等待函数调用。这样的检测仍然只进行一次,但在此过程中来得更早,例如:
var addHandler=document.body.addEventListener?function(target,eventType,handler){
target.addEventListener(eventType,handler,false);
}:function(target,eventType,handler){
target.attachEvent("on"+eventType,handler);
};
var removeHandler=document.body.removeEventListener?function(target,eventType,handler){
target.removeEventListener(eventType,handler,false);
}:function(target,eventType,handler){
target.detachEvent("on"+eventType,handler);
};
在上面代码中,先检查addEventListener和removeEventListener是否存在,然后根据信息指定最合适的函数。三元操作符返回DOM级别2的函数,如果它们不存在,则返回IE特有的函数。虽然检测功能提前了,但是接下来调用addHandler和removeHandler同样很快。条件预加载确保所有函数调用时间相同,其代价是在脚本加载时进行检测。预加载适用于一个函数马上就会被用到且在整个页面生命周期中经常会被使用的场合。