惰性载入函数由来
惰性载入函数的概念,最早见于《javascript高级程序设计》这本书;去年某个时候,自己偶然翻到了这一章;忽然感觉挺有道理的。最近呢,老是接触ajax这东东,我们知道浏览器之间行为的差异造成我们使用ajax,特别是创建XHR对象时,使用了大量的if判断,来做兼容性的处理。所以再次细细咀嚼了一下,写一篇博客分享再次强化。
常见的创建XHR对象的方式,类似如下代码:
//创建XHR对象 function createXHR() { if (typeof XMLHttpRequest != "undefined") { console.log("XMLHttpRequest,我被返回了一次"); 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"]; var length = versions.length; for (var i = 0; i < length; i++) { try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex) { //todo something } } } console.log("ActiveXObject,我被返回了一次"); return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available."); } }
不知道,你有没有注意到,我们每次调用createXHR的时候,整个代码的逻辑判断都要走一遍直到浏览器支持某个对象,然后返回。(如果都没有就抛出一个错误)。
如下测试:
var xhr = createXHR(); var xhr2 = createXHR(); var xhr3 = createXHR();
结果如下图:
假如,一个应用里面调用了50,100次createXHR是不是就会按照上面的逻辑走50,100次。其实,我们应该这样想,如果浏览器支持,它就会一直支持;那么我们大量的if进行测试就显得没有必要,甚至多余了。从性能的角度来看,没有if条件判断肯定比含有if判断的快;
当然,我们上面的代码已经做了一次优化了。怎么说呢?
在上面代码中我们有意识的把判断XMLHttpRequest对象放在if首位;因为我们知道大部分浏览器支持XMLHttpRequest,自然也就进入if逻辑里面返回XMLHttpRequest,不必要接着做if判断。当然,如果你把XMLHttpRequest非首位,甚至放在最后,那就会增加if判断,自然性能比放在首位差一些。所以呢,在做浏览器功能检测时,我们常常把大部分浏览器支持的判断放在首位。
代码大概如下:
function isSupport(feature) { if () {//兼容大部分浏览器 //todo something } else if () {//兼容少部分浏览器 //todo something } else if() {//j极少数的浏览器 //todo something } else { //todo something } }
有点扯远了,我们回来。为了少进行if逻辑判断,甚至不进行或者进行一次;我们需要借助函数,来实现所谓的“惰性载入函数”。
惰性载入函数最佳实践(1)
惰性载入,表示函数执行的分支进入发生一次。差不多有两种方式实现惰性载入。
在函数被调用时,再处理函数;什么意思呢,在第一次调用函数的过程中,该函数会被覆盖为另一个分支的处理函数,这样任何对函数的再次调用就不用再一次测试,判断,执行相关分支的代码。
上面创建XHR的方式,使用第一种“惰性载入”可以这样重写。
代码如下:
//第一种惰性载入的方式 function createXHR() { if (typeof XMLHttpRequest != "undefined") { console.log("XMLHttpRequest,我被返回了一次"); createXHR = function () { return new XMLHttpRequest(); } } else if (typeof ActiveXObject != "undefined") { console.log("ActiveXObject,我被返回了一次"); createXHR = function() { if (typeof arguments.callee.activeXString != "string") { var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"]; var length = versions.length; for (var i = 0; i < length; i++) { try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex) { //todo something } } } return new ActiveXObject(arguments.callee.activeXString); } } else { createXHR = function() { throw new Error("No XHR object available."); } } return createXHR(); }
上面代码,if的每一次分支都会为createXHR变量复制,重写;关键的最后一步,返回调用新复制的函数。这样下一次我们在调用createXHR时,就会直接调用被复制冲洗的函数,不用再次if判断返回了。
你可以尝试这样调用下一个。
代码如下:
var xhr = createXHR(); var xhr2 = createXHR(); var xhr3 = createXHR();
结果如下图:
惰性载入函数最佳实践(2)
第二种实现惰性载入的方式是,声明函数时,就留用自执行函数返回指定的函数。这样旨在函数自执行是损失一点性能做功能检测;后面不管时,第一次被调用,还是第N次被调用就不用做功能检测,性能损失降到最低。
我们再次重写createXHR。
代码如下:
//第二种惰性载入的方式 var createXHR = (function() { if (typeof XMLHttpRequest != "undefined") { console.log("XMLHttpRequest,我被返回了一次"); return function () { return new XMLHttpRequest(); } } else if (typeof ActiveXObject != "undefined") { console.log("ActiveXObject,我被返回了一次"); return function() { if (typeof arguments.callee.activeXString != "string") { var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"]; var length = versions.length; for (var i = 0; i < length; i++) { try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex) { //todo something } } } return new ActiveXObject(arguments.callee.activeXString); } } else { return function() { throw new Error("No XHR object available."); } } })();
接着我们调用一下,代码如下:
var xhr = createXHR(); var xhr2 = createXHR(); var xhr3 = createXHR();
果真如预料的一样调用了三次都没有再执行if进行功能检测了。
好了,代码我就不多做解释了,我相信能静下心来看到这里的道友们,肯定已经很熟悉闭包,函数自执行等等了。
写在最后
“惰性载入”是继上一篇博客“作用域安全的构造函数”之后的有一个高级技巧。其优点是,只在首次执行代码时损失一点性能做功能检测。上面两种方式哪一种更适合你,看你而定。