这个JavaScript/jQuery语法是如何工作的:(function(window, undefined) { })(window)?

160

你是否曾经看过 jQuery 1.4 源代码的内部结构并注意到它是如何被封装的:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

我读过一篇关于 JavaScript 命名空间 的文章和另一篇名为 "括号的重要配对" 的文章,所以我了解一些这里的情况。

但我从未见过这种特殊的语法。那个undefined在那里做什么?为什么需要传递 window,然后再次出现在最后?


3
我想补充一下,保罗·艾瑞什在这个视频中谈到了这个问题:http://paulirish.com/2010/10-things-i-learned-from-the-jquery-source/。 - Mottie
@Bergi,您将我的问题标记为另一个问题的重复,但是我提出了这个问题几乎一年之前。转换应该反过来。 - dkinzer
@dkinzer:这不是早些时候被问到的问题,而是关于最高质量答案的问题。诚然,在这种情况下,两个答案都很接近,但我认为CMS的答案是最好的。请来http://chat.stackoverflow.com/rooms/17/讨论此事。 - Bergi
5个回答

164

undefined是一个普通变量,可以通过 undefined = "new value"; 简单地更改。因此,jQuery创建了一个本地的“undefined”变量,它真正的值是未定义的。

window变量被局部化以提高性能。因为当JavaScript查找一个变量时,它首先通过局部变量,直到找到变量名。如果没有找到,则JavaScript继续查找下一个范围,直到过滤出全局变量。因此,如果将window变量局部化,JavaScript可以更快地查找它。 更多信息:Speed Up Your JavaScript - Nicholas C. Zakas


1
谢谢!那是一个非常有信息量的视频。我认为你需要编辑你的回答,说“窗口变量被设为本地变量”。我认为这是最好的答案。我数了一下,在 JQuery 源代码中至少调用了 17 次 window 对象。因此,将窗口移动到本地作用域中肯定会产生重大影响。 - dkinzer
1
根据这个更新的测试 http://jsperf.com/short-scope/5 ,通过jQuery获取窗口常量时,传递 'window' 实际上更慢,尤其是在Safari浏览器中表现特别差。因此,虽然 'undefined' 有用例,但我并不完全相信 'window' 在所有情况下都特别有用。 - hexalys
1
这也对代码的缩小有积极的影响。因此,通过缩小器,可以用更短的名称替换每个“window”和“undefined”的使用。 - TLindig
@dkinzer,你有任何支持你的显著性的统计数据吗? - Akash Kava
2
当你在制作被数百万网站使用的库时,明智的做法是不要对疯子们会做什么进行假设。 - JLRishe
显示剩余2条评论

55

未定义

通过将undefined声明为参数,但从未传递值给它,确保它始终是未定义的,因为它只是全局范围内可以被覆盖的变量。这使a === undefined成为typeof a == 'undefined'的安全替代品,可以节省一些字符。它还使代码更易于缩小,因为例如undefined可以缩短为u,从而再次节省了几个字符。

窗口

window作为参数传递会在本地范围内保留一个副本,这会影响性能:http://jsperf.com/short-scope。现在,对window的所有访问都必须向上移动一个级别。与undefined一样,再次使用本地副本可进行更激进的缩小。


附注:

虽然这可能不是jQuery开发人员的意图,但传递window使该库更容易集成到服务器端JavaScript环境中,例如node.js - 在那里没有全局window对象。在这种情况下,只需要更改一行即可将window对象替换为另一个对象。对于jQuery而言,可以创建一个虚拟的window对象并传递给它,以便进行HTML抓取(例如jsdom这样的库可以做到这一点)。


2
+1 提到了代码压缩,真希望我能 +2 提到 jsperf -- 压缩后的版本是 (function(a,b){})(window); -- abwindowundefined 短得多 :) - gnarf
@gnarf,感谢您通知我这些问题已经合并。我会编辑我的答案,使其更加合理。;) - David Tang
顺便提一下,在ECMAScript3中,全局的window.undefined可以被覆盖,但在ECMAScript5中不允许这样做,现在大多数浏览器都运行在ECMAScript5标准下。我猜IE7允许覆盖未定义的全局变量。IE8+没问题。 - Arman
那么在没有 window 的脚本中,var window = {}; 不会起到同样的作用吗? - Akash Kava
你可以将 this 传递给 IIFE,而不是 window,以在浏览器和 node.js 环境中获取全局作用域。例如:(function (root, undefined) { 'use strict'; var $ = root.jQuery; }(this)); - Lars Gyrup Brink Nielsen
显示剩余3条评论

16

其他人已经解释了undefinedundefined就像是一个全局变量,可以重新定义为任何值。这种技术的目的是防止所有未定义检查中断,如果有人在某处写了undefined = 10,那么这个技巧就能防止出现问题。一个从未传递的参数一定是真正的undefined,无论变量undefined的值是什么。

传递window的原因可以通过以下示例说明。

(function() {
   console.log(window);
   ...
   ...
   ...
   var window = 10;
})();

控制台会输出什么?是 window 对象的值吗?不对!是 10 吗?也不对!它会输出 undefined。JavaScript 解释器(或者 JIT 编译器)会将其重写为:

(function() {
   var window; //and every other var in this function

   console.log(window);
   ...
   ...
   ...
   window = 10;

})();

然而,如果您将window变量作为参数获取,则没有var,因此不会有任何意外情况。

我不知道jQuery是否在这样做,但是如果您出于任何原因在函数中重新定义了window局部变量,从全局范围中继承它是个好主意。


6

window这样传递是为了防止有人在IE中重新定义窗口对象,我想undefined也是同样的原因,以防它被后续某种方式重新分配。

脚本中的顶级window只是给参数“window”命名,这个参数比全局window引用更本地,并且它是这个闭包内部使用的。末尾的window实际上指定了要传递的第一个参数,在这种情况下是window的当前含义......希望在此之前您还没有破坏window

可能更容易理解的方法是展示jQuery中最典型的用例,即插件.noConflict()处理,因此对于大多数代码,即使在此范围之外,$仍然可以使用,即使它表示的是除jQuery之外的其他东西:

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);

谢谢。这很有道理。虽然我认为答案是这个和@C.Zakas所说的结合。 - dkinzer
@DKinzer,恐怕我不是C. Zakas ;) - Vincent
@Vincent,抱歉 :-) - dkinzer

5

进行了1000000次迭代测试。这种本地化对性能没有影响。在1000000次迭代中,甚至没有花费一毫秒的时间。这是毫无用处的。


2
更不用说,闭包会随着函数实例一起携带所有变量,因此如果您有100个字节的闭包,并且有1000个实例,则需要100kb或更多的内存。 - Akash Kava
@AkashKava 和 @Semra,我认为这个测试并不能真正说明在实际情况下为什么你会传入 window。性能提升只有在你有深度嵌套的闭包进一步访问该变量时才会看到。显然,如果您的函数与变量在作用域链上仅相隔一步,您不会看到太多的收益。但是,如果它在很远的地方并且需要获取window,那么通过本地副本可能会看到一些收益。 - gabereal
@gabereal 新的JavaScript引擎在编译时就解决了闭包问题,因此闭包在运行时并不会带来性能提升。我怀疑是否有任何可衡量的性能提升。如果你非常有信心,可以创建jsperf示例来展示性能。我们唯一获得的性能提升是通过隔离闭包,从而实现更好的缩小,减小大小,并通过更快的下载和解析脚本来实现性能优化。 - Akash Kava
@AkashKava,是什么让你认为我非常自信?我只是说“可能会看到一些收益”和“我不认为”。我可以创建一个测试,但我宁愿不这样做,因为我从来不使用这种模式。你能解释一下你所说的“在编译时解决闭包”的意思吗?相对于什么样的替代方案?以前JavaScript引擎是如何做事情的? - gabereal
有一个查找代码的问题,其中窗口在最后被柯里化,但没有输入到函数的参数中,这是什么意思?它确保参数遵循原始全局作用域对象,即使没有窗口参数,我猜测是这样吗? - Webwoman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接