惰性载入函数由来

惰性载入函数的概念,最早见于《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,100createXHR是不是就会按照上面的逻辑走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();

结果如下图:

惰性载入函数_惰性载入函数_02



惰性载入函数最佳实践(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进行功能检测了。

 惰性载入函数_惰性载入函数_03

    好了,代码我就不多做解释了,我相信能静下心来看到这里的道友们,肯定已经很熟悉闭包,函数自执行等等了。



写在最后

“惰性载入”是继上一篇博客“作用域安全的构造函数”之后的有一个高级技巧。其优点是,只在首次执行代码时损失一点性能做功能检测。上面两种方式哪一种更适合你,看你而定。