在看一些 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
的次数从而提升了性能。这种方法在实际代码中效率可以高出很多。所以优化代码的执行性能还是非常有必要的。完。