函数的惰性载入,在Js中很常见的一种编码现象。由于各大浏览器的差异,我们在实现一项功能的时候需要考虑不同浏览器之间的兼容性问题,因此需要进行浏览器嗅探。最常用的嗅探方法就是通过if else的方式判断当前环境是否支持某一对象。例如,使用原生js创建XHR对象:
function createXHR(){
if(typeof XMLHttpRequest != 'undefined'){
return new XMLHttpRequest();
}else if(typeof ActiveXObject != 'undefined'){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],i,len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}else{
throw new Error('No XHR object available.')
}
}
每次调用createXHR()的时候,他都会对浏览器所支持的能力进行检测:首先检测内置的XHR,如果没有内置XHR,测试有没有基于ActiveX的XHR,最后如果都没有发现的话就抛出一个异常。每次调用该函数都会重复以上步骤,即使每次调用时分支结果都不变。如果浏览器支持内置的XHR,那么它就是一直支持了,那么这种重复的检测就变的没有必要了。在JS中,即使只有一个if语句的判断,也肯定要比没有if语句的慢,所以如果if语句不必内次都执行,那么代码就可以在一定程度上运行的更快。解决方案就是称之为惰性载入的技巧。
惰性载入表示函数执行的分支仅会发生一次。有两种实现惰性载入的方式,第一种就是在函数被调用时在处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适的方式执行的函数,这样任何对原函数的调用都不会再经过执行的分支了。例如,可以用下面的方式使用惰性载入重写createXHR函数:
function createXHR(){
if(typeof XMLHttpRequest != 'undefined'){
createXHR = function(){
return new XMLHttpRequest();
};
}else if(typeof ActiveObject != 'undefined'){
createXHR = function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
}else{
createXHR = function(){
throw new Error('NO XHR object available.');
}
}
return createXHR();
}
在这个惰性载入的createXHR()中,if语句的每一个分支都会为creaeteXHR变量赋值,有效覆盖了原有的函数。最后一步便是调用新赋值的函数。下一次调用createXHR()的时候,就会直接调用被分配的函数。之后就不会再次执行if语句进行识别了。
第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样,第一次调用函数就不会损失性能了,而在代码首次加载时会损失一点性能。例如,下面这个例子:
var createXHR = (function(){
if(typeof XMLHttpRequest != 'undefined'){
return function(){
return new XMLHttpRequest();
}
}else if(typeof ActiveObject != 'undefined'){
return function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
}else {
return function(){
throw new Error('NO XHR object available.');
};
}
})();
这个例子中使用的技巧是创建一个匿名、自执行的函数,用以确定应该以哪一个函数实现。实际的逻辑都一样。不一样的地方就是第一行代码(使用var 定义函数)、新增了自执行的匿名函数,另外每个分支都返回正确的函数定义,以便立即将其赋值给createXHR()。
惰性载入函数的有点是只在首次执行分支代码时牺牲一点性能,在以后再次调用时,可以避免执行不必要的分支检测。