Serenader

Learning by sharing

(function(window, undefined){})(window)这种写法的原理

在看一些 jQuery 插件的时候经常会看到这样的写法:
(function(window, undefined){
...
//code goes here
})(window)
或者这样的:
(function($, window, undefined){
...
//code goes here
})(jQuery, window)
其实上面两个例子从根本上讲是一样的。
为何要在匿名函数中传入 $ 和 window 和undefined 形参呢?
先来说说前两个形参。
JavaScript 的解析器解析代码其实是从里到外解析的,也就是说,解析器会先解析函数内部的代码,然后再解析函数外部的代码。这样的话,如果我们在函数内部引用了 $ 或者 window 对象的话,每一次引用解析器都要跑到函数外面去查找其相应的对象。为了使得解析效率更高,有高人想出了这种写法: 把 jQuery 和 window 作为参数传递进去。也就是我们上面的例子了。上面的例子是匿名自执行函数,当函数定义好之后立即执行,然后将 jQuery 和 window 作为参数传递进去。这样一来在函数内部就可以直接引用 jQuery 和 window 了,而不用再从函数外部查找其对象。
来看几个例子:
(function () {
	window.property1 = 1;
    window.property2 = 2;
    window.property3 = 3;
    ...
})();

(function () {
	var tmp = window;
    tmp.property1 = 1;
    tmp.property2 = 2;
    tmp.property3 = 3;
    ...
})();

(function (window) {
	window.property1 = 1;
    window.property2 = 2;
    window.property3 = 3;
    ...
})(window);
上面三个例子其实做的都是同一件事情,即为 window 对象添加属性。但是三者效率有些差别。第一种就是最普通的方法,即直接在函数内部调用 window 对象,函数没有传递参数。第二种则是在函数内部先缓存了 window 对象,然后每次需要调用 window 对象时直接调用这个 tmp 。第一个例子中的函数内部如果又语句引用了 window 对象,解析器都要从函数外部查找这个对象,因为 window 对象是全局对象,不在函数内部定义。而第二个例子先把 window 对象保存在一个局部变量 tmp 中,这样方便后面的引用。从效率来讲,第二种方法会比第一种方法快得多,因为第二种方法只需从外部查找一次 window 全局变量。之后每次需要引用 window 对象时可以直接引用其自身的局部变量。引用局部变量自然会比引用全局变量快得多,因为解析器是由里到外查询变量的。
然后第三个例子其实就是我们所讲的方法。其效率应该和第二个例子差不多。所以以后再写代码的时候尽可能按照这种方法去写,可以提高脚本运行效率。
最后面的形参 undefined 又是怎么回事呢?
原来在 JavaScript 中 undefined 并不是作为一个关键字存在的。换句话说,我们可以给 undefined 赋值,而且不会出错。看例子:
var undefined = 1;
alert(undefined);//1
从例子可以看出 undefined 是可以认为修改它的值的。在一些插件或者代码中我们多多少少会引用到 undefined 这个对象。那么如果在全局环境中 undefined 被修改了它的值的话,那么插件或者代码可能就会导致意想不到的错误。为了避免由于重写 undefined 的值而带来的麻烦,又有高人想出了这种解决方法,其实就是上面第一个例子。把 undefined 作为形参传递进入,然后执行的时候不传递值。这样做会导致什么后果?
由于执行的时候没有给 undefined 传递值,那么在函数内部 undefined 对象的值刚刚好就是我们所想要的 undefined !来看例子:
var undefined = 1;
(function (undefined){
	alert(undefined);//undefined
})();
结果为 undefined ,表明我们的目的达到了。这样就能避免函数内部不受外部变量的干扰,使得 undefined 对象的值永远为 undefined 。

类似这种例子的还有我们经常碰到的,就是在 for 循环语句中的。看例子:
var arr = [1,2,3,4,5,6,7,8,9,10];
for( var i = 0; i < arr.length; i++) {
	...//code goes here
}
相信这种写法大家多多少少会接触到。其实这也是效率低的一种体现。因为每次循环解析器队徽比较 i 和 arr.length 的大小,而每次引用 arr.length 解析器都会重新计算一次 arr 数组的长度。所以说,以上代码运行的时候其实会访问 arr 数组10次。但是很明显我们并不想这样,因为这样完全没必要,浪费性能。所以才有了下面的这种写法:
var arr = [1,2,3,4,5,6,7,8,9,10];
for( var i = arr.length; i > 0; i--) {
	...//code goes here
}

var arr = [1,2,3,4,5,6,7,8,9,10];
for( var i = 0, l = arr.length; i < l; i++) {
	...//code goes here
}
上面的例子其实都是一个意思,都是在循环体内部先缓存了 arr.length , 这样的好处是需要引用到这个对象的时候不用再去计算 arr 的长度了,而是可以直接引用变量,减少了访问 arr 的次数从而提升了性能。这种方法在实际代码中效率可以高出很多。所以优化代码的执行性能还是非常有必要的。
完。