我经常在JavaScript源代码中看到这种写法,但我从未真正了解为什么要使用这种结构。这有什么必要吗?
(function() {
//stuff
})();
为什么要这样写?为什么不直接使用stuff
而不是在函数中使用它?
编辑:我知道这是定义一个匿名函数然后调用它,但是为什么?
我经常在JavaScript源代码中看到这种写法,但我从未真正了解为什么要使用这种结构。这有什么必要吗?
(function() {
//stuff
})();
为什么要这样写?为什么不直接使用stuff
而不是在函数中使用它?
编辑:我知道这是定义一个匿名函数然后调用它,但是为什么?
它用于创建一个带有私有功能和变量的函数闭包,这些功能和变量不是全局可见的。
考虑以下代码:
(function(){
var test = true;
})();
变量test
只在函数闭包内部可见,而在其他地方是不可见的。
函数闭包使得各种脚本之间不会相互干扰,即使它们定义了同名的变量或私有函数。这些私有内容仅在闭包内部可见和访问,而在闭包外部则无法访问。
请查看以下代码并阅读其注释:
// public part
var publicVar = 111;
var publicFunc = function(value) { alert(value); };
var publicObject = {
// no functions whatsoever
};
// closure part
(function(pubObj){
// private variables and functions
var closureVar = 222;
var closureFunc = function(value){
// call public func
publicFunc(value);
// alert private variable
alert(closureVar);
};
// add function to public object that accesses private functionality
pubObj.alertValues = closureFunc;
// mind the missing "var" which makes it a public variable
anotherPublic = 333;
})(publicObject);
// alert 111 & alert 222
publicObject.alertValues(publicVar);
// try to access varaibles
alert(publicVar); // alert 111
alert(anotherPublic); // alert 333
alert(typeof(closureVar)); // alert "undefined"
这里有一个JSFiddle运行代码,根据上面的注释显示数据。
正如你已经知道的那样,它:
创建一个函数:
function() { ... }
并立即执行它: (func)();
这个函数可能会或可能不会接受额外的参数。
通常,jQuery 插件是通过定义一个带有一个参数的函数来实现的,插件在其中进行操作:
(function(paramName){ ... })(jQuery);
但主要思想仍然相同:使用私有定义定义一个函数闭包,该闭包不能直接在外部使用。
这种构造被称为自执行匿名函数,其实并不是一个很好的名字,下面是它的运作方式(以及为什么这个名字不太好)。这样做:
function abc() {
//stuff
}
定义一个名为abc
的函数,如果我们想要一个匿名函数(这是JavaScript中非常常见的一种模式),它可能会是以下内容:
function() {
//stuff
}
但是,如果你有这个匿名函数,你要么需要将其与一个变量关联起来,这样你就可以调用它(这将使其不那么匿名),要么你需要立即执行它。我们可以通过以下方式尝试立即执行它:
function() {
//stuff
}();
但这样做将无法起作用,因为它会导致语法错误。你得到语法错误的原因是这样的。当你创建一个带名称的函数(比如上面的abc),该名称变成了对函数表达式的引用,你可以通过在名称后加上 () 来执行表达式,例如:abc()
。声明函数本身不会创建表达式,函数声明实际上是一个语句而不是表达式。本质上,表达式是可执行的,而语句则不是(您可能已经猜到了)。因此,为了执行匿名函数,您需要告诉解析器它是一个表达式而不是语句。一种方法(不是唯一的方法,但已成为惯例)是将您的匿名函数包装在一组 ()
中,因此您得到以下结构:
(function() {
//stuff
})();
一个立即执行的匿名函数(可以看出这个构造名称有点不准确,因为它并不是一个自我执行的匿名函数,而是一个立即执行的匿名函数)。
那么为什么这些都很有用呢?一个原因是它可以防止代码污染全局命名空间。由于 JavaScript 中的函数具有它们自己的作用域,函数内部的任何变量都不会在全局范围内可见,因此如果我们能够以某种方式将所有代码都写在一个函数内部,全局作用域将是安全的,而我们的自执行匿名函数正好可以实现这一点。让我借用 John Resig 老书中的一个例子:
// Create a new anonymous function, to use as a wrapper
(function(){
// The variable that would, normally, be global
var msg = "Thanks for visiting!";
// Binding a new function to a global object
window.onunload = function(){
// Which uses the 'hidden' variable
alert( msg );
};
// Close off the anonymous function and execute it
})();
我们所有的变量和函数都是写在自执行匿名函数内部的,因为它在自执行匿名函数内部,所以我们的代码首先被执行。由于JavaScript允许闭包,也就是允许函数访问在外部函数定义的变量,我们几乎可以在自执行匿名函数内部编写任何代码,并且一切都会按预期工作。
但等等,这里还有更多的好处 :). 这种结构允许我们解决使用JavaScript中闭包时经常会出现的问题。我再次引用John Resig的话:
请记住,闭包允许您引用存在于父函数中的变量。但是,它不提供创建该变量时的值;它提供父函数内变量的最后一个值。您将看到这种情况最常见的问题发生在for循环中。有一个变量被用作迭代器(例如i)。在for循环内部,正在创建使用闭包引用迭代器的新函数。问题在于,当调用新闭合函数时,它们将引用迭代器的最后一个值(即数组中的最后一个位置),而不是您期望的值。图2-16显示了使用匿名函数诱导范围的示例,创建实例,其中可能存在预期的闭包。
// An element with an ID of main
var obj = document.getElementById("main");
// An array of items to bind to
var items = [ "click", "keypress" ];
// Iterate through each of the items
for ( var i = 0; i < items.length; i++ ) {
// Use a self-executed anonymous function to induce scope
(function(){
// Remember the value within this scope
var item = items[i];
// Bind a function to the element
obj[ "on" + item ] = function() {
// item refers to a parent variable that has been successfully
// scoped within the context of this for loop
alert( "Thanks for your " + item );
};
})();
}
简而言之,所有这些都意味着,人们经常编写像这样的天真的javascript代码(这是上面循环的天真版本):
for ( var i = 0; i < items.length; i++ ) {
var item = items[i];
// Bind a function to the elment
obj[ "on" + item ] = function() {
alert( "Thanks for your " + items[i] );
};
}
我们在循环中创建的函数是闭包,但不幸的是它们会锁定外部作用域中 i
的最后一个值(在这种情况下,它可能是2,这会引起问题)。我们想要的可能是每个在循环内创建的函数都锁定我们创建时的i值。这就是我们自执行匿名函数的用处,下面是一种类似但更易理解的重写循环的方式:
for ( var i = 0; i < items.length; i++ ) {
(function(index){
obj[ "on" + item ] = function() {
alert( "Thanks for your " + items[index] );
};
})(i);
}
因为我们在每次迭代时调用匿名函数,所以我们传递的参数被锁定为传递时的值,因此我们在循环内创建的所有函数都将按预期工作。这个用法是定义一个匿名函数并立即调用它。我猜在代码块周围加括号的原因是JavaScript需要它们来理解函数调用。
如果你想在现场定义一个一次性函数,然后立即调用它,这个用法非常有用。使用匿名函数和直接编写代码之间的区别在于作用域。所有匿名函数中的变量都会在函数结束时失去作用域(除非变量另有说明)。这可以用于保持全局或封闭命名空间的清洁,长期使用更少的内存,或获得一些“隐私”。
这是一个“匿名自执行函数”或“立即调用函数表达式”。Ben Alman在这里给出了很好的解释。
我在创建命名空间时使用这种模式。
var APP = {};
(function(context){
})(APP);
这种结构在编程中非常有用,特别是当您想要创建闭包时 - 该结构可帮助创建一个专门的“房间”,使变量无法从外部访问。详见“JavaScript精粹”一书中的这一章节: http://books.google.com/books?id=PXa2bby0oQ0C&pg=PA37&lpg=PA37&dq=crockford+closure+called+immediately&source=bl&ots=HIlku8x4jL&sig=-T-T0jTmf7_p_6twzaCq5_5aj3A&hl=lv&ei=lSa5TaXeDMyRswa874nrAw&sa=X&oi=book_result&ct=result&resnum=1&ved=0CBUQ6AEwAA#v=onepage&q&f=false
在第38页顶部显示的示例中,您可以看到变量“status”被隐藏在闭包内,除了调用get_status()
方法外无法访问。我不确定这个问题是否已经被回答了,如果我只是在重复的话,请原谅。
在JavaScript中,只有函数引入新的作用域。通过将代码包装在立即函数中,您定义的所有变量仅存在于此或更低的作用域中,但不在全局作用域中。
因此,这是一个不污染全局作用域的好方法。
应该只有很少的全局变量。请记住,每个全局变量都是window
对象的属性,该对象默认已经有很多属性。引入新的作用域也可以避免与window
对象的默认属性发生冲突。