你们学JavaScript的时候,常常遇到自执行匿名函数的代码,今天咱们主要就来想一想说一下自执行。javascript
在详细了解这个以前,咱们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法也不必定彻底对,主要是看我的如何理解,由于有的人说当即调用,有的人说自动执行,因此你彻底能够按照你本身的理解来取一个名字,不过我听不少人都叫它为“自执行”,但做者后面说了不少,来讲服你们称呼为“当即调用的函数表达式”。html
本文英文原文地址:http://benalman.com/news/2010/11/immediately-invoked-function-expression/java
在JavaScript里,任何function在执行的时候都会建立一个执行上下文,由于为function声明的变量和function有可能只在该function内部,这个上下文,在调用function的时候,提供了一种简单的方式来建立自由变量或私有子function。git
// 因为该function里返回了另一个function,其中这个function能够访问自由变量i // 全部说,这个内部的function其实是有权限能够调用内部的对象。 function makeCounter() { // 只能在makeCounter内部访问i var i = 0; return function () { console.log(++i); }; } // 注意,counter和counter2是不一样的实例,分别有本身范围内的i。 var counter = makeCounter(); counter(); // logs: 1 counter(); // logs: 2 var counter2 = makeCounter(); counter2(); // logs: 1 counter2(); // logs: 2 alert(i); // 引用错误:i没有defind(由于i是存在于makeCounter内部)。
不少状况下,咱们不须要makeCounter多个实例,甚至某些case下,咱们也不须要显示的返回值,OK,往下看。github
当你声明相似function foo(){}或var foo = function(){}函数的时候,经过在后面加个括弧就能够实现自执行,例如foo(),看代码:express
// 由于想下面第一个声明的function能够在后面加一个括弧()就能够本身执行了,好比foo(), // 由于foo仅仅是function() { /* code */ }这个表达式的一个引用 var foo = function(){ /* code */ } // ...是否是意味着后面加个括弧均可以自动执行? function(){ /* code */ }(); // SyntaxError: Unexpected token ( //
上述代码,若是运行,第2个代码会出错,由于在解析器解析全局的function或者function内部function关键字的时候,默认是认为function声明,而不是function表达式,若是你不显示告诉编译器,它默认会声明成一个缺乏名字的function,而且抛出一个语法错误信息,由于function声明须要一个名字。segmentfault
有趣的是,即使你为上面那个错误的代码加上一个名字,他也会提示语法错误,只不过和上面的缘由不同。在一个表达式后面加上括号(),该表达式会当即执行,可是在一个语句后面加上括号(),是彻底不同的意思,他的只是分组操做符。闭包
// 下面这个function在语法上是没问题的,可是依然只是一个语句 // 加上括号()之后依然会报错,由于分组操做符须要包含表达式 function foo(){ /* code */ }(); // SyntaxError: Unexpected token ) // 可是若是你在括弧()里传入一个表达式,将不会有异常抛出 // 可是foo函数依然不会执行 function foo(){ /* code */ }(1); // 由于它彻底等价于下面这个代码,一个function声明后面,又声明了一个毫无关系的表达式: function foo(){ /* code */ }(1);
你能够访问ECMA-262-3 in detail. Chapter 5\. Functions 获取进一步的信息。ecmascript
要解决上述问题,很是简单,咱们只须要用大括弧将代码的代码所有括住就好了,由于JavaScript里括弧()里面不能包含语句,因此在这一点上,解析器在解析function关键字的时候,会将相应的代码解析成function表达式,而不是function声明。不明白的,能够看深刻理解JavaScript系列2:揭秘命名函数表达式中的函数表达式和函数声明函数
// 下面2个括弧()都会当即执行 (function () { /* code */ } ()); // 推荐使用这个 (function () { /* code */ })(); // 可是这个也是能够用的 // 因为括弧()和JS的&&,异或,逗号等操做符是在函数表达式和函数声明上消除歧义的 // 因此一旦解析器知道其中一个已是表达式了,其它的也都默认为表达式了 // 不过,请注意下一章节的内容解释 var i = function () { return 10; } (); true && function () { /* code */ } (); 0, function () { /* code */ } (); // 若是你不在乎返回值,或者不怕难以阅读 // 你甚至能够在function前面加一元操做符号 !function () { /* code */ } (); ~function () { /* code */ } (); -function () { /* code */ } (); +function () { /* code */ } (); // 还有一个状况,使用new关键字,也能够用,但我不肯定它的效率 // http://twitter.com/kuvos/status/18209252090847232 new function () { /* code */ } new function () { /* code */ } () // 若是须要传递参数,只须要加上括弧()
上面所说的括弧是消除歧义的,其实压根就不必,由于括弧原本内部原本指望的就是函数表达式,可是咱们依然用它,主要是为了方便开发人员阅读,当你让这些已经自动执行的表达式赋值给一个变量的时候,咱们看到开头有括弧(,很快就能明白,而不须要将代码拉到最后看看到底有没有加括弧。
和普通function执行的时候传参数同样,自执行的函数表达式也能够这么传参,由于闭包直接能够引用传入的这些参数,利用这些被lock住的传入参数,自执行函数表达式能够有效地保存状态。
下面是错误的使用:
var elems = document.getElementsByTagName('a'); for (var i = 0; i < elems.length; i++) { elems[i].addEventListener('click', function (e) { e.preventDefault(); alert('I am link #' + i); }, 'false'); }
因为变量i
历来就没背locked住。相反,当循环执行之后,咱们在点击的时候i得到数值,因此说不管点击哪一个链接,最终显示的都是I am link #10(若是有10个a元素的话)
下面是正确的使用:
var elems = document.getElementsByTagName('a'); for (var i = 0; i < elems.length; i++) { (function (lockedInIndex) { elems[i].addEventListener('click', function (e) { e.preventDefault(); alert('I am link #' + lockedInIndex); }, 'false'); })(i); }
因为在自执行函数表达式闭包内部i的值做为locked的索引存在,在循环执行结束之后,尽管最后i的值变成了a元素总数(例如10)但闭包内部的lockedInIndex值是没有改变,由于他已经执行完毕了因此当点击链接的时候,结果是正确的。
或者你也能够像这样使用:
var elems = document.getElementsByTagName('a'); for (var i = 0; i < elems.length; i++) { elems[i].addEventListener('click', (function (lockedInIndex) { return function (e) { e.preventDefault(); alert('I am link #' + lockedInIndex); }; })(i), 'false'); }
上面的代码在处理函数那里使用自执行函数表达式,而不是在addEventListener外部,这样也能够达到locked的效果,可是前面的代码更具备可读性。
其实,前面两个例子里的lockedInIndex变量,也能够换成i,由于和外面的i不在一个做用于,因此不会出现问题,这也是匿名函数+闭包的威力。
在这篇文章中,咱们一直叫自执行函数,确切的说是自执行匿名函数(Self-executing anonymous function)
,但英文原文做者一直倡议使用当即调用的函数表达式(Immediately-Invoked Function Expression)
这一名称,做者又举了一堆例子来解释,好吧,咱们来看看:
// 这是一个自执行的函数,函数内部执行自身,递归 function foo() { foo(); } // 这是一个自执行的匿名函数,由于没有函数名称 // 必须使用arguments.callee属性来执行本身 var foo = function () { arguments.callee(); }; // 这可能也是一个自执行的匿名函数,仅仅是foo函数名引用它自身 // 若是你将foo改变成其它的,你将获得一个自执行(used-to-self-execute)的匿名函数 var foo = function () { foo(); }; // 有些人叫这个是自执行的匿名函数(即使它不是),由于它没有调用自身,它只是当即执行而已。 (function () { /* code */ } ()); // 为函数表达式添加一个函数名称,能够方便Debug // 注意:一旦命名为函数添加了函数名,这个函数就再也不是匿名的了 (function foo() { /* code */ } ()); // 当即调用的函数表达式(IIFE)也能够自执行,不过可能不经常使用罢了 (function () { arguments.callee();} ()); (function foo() { foo(); } ());
但愿这里的一些例子,可让你们明白,什么叫自执行,什么叫当即调用。
注:arguments.callee在ECMAScript 5 strict mode里被废弃了,因此在这个模式下,实际上是不能用的。
在讲到这个当即调用的函数表达式的时候,我又想起来了Module模式,若是你还不熟悉这个模式,咱们先来看看代码:
// 建立一个当即调用的匿名函数表达式 // return一个变量,其中这个变量里包含你要暴露的东西 // 返回的这个变量将赋值给counter,而不是外面声明的function自身 var counter = (function () { var i = 0; return { get: function () { return i; }, set: function (val) { i = val; }, increment: function () { return ++i; } }; } ()); // counter是一个带有多个属性的对象,上面的代码对于属性的体现实际上是方法 counter.get(); // 0 counter.set(3); counter.increment(); // 4 counter.increment(); // 5 counter.i; // undefined 由于i不是返回对象的属性 i; // 引用错误: i 没有定义(由于i只存在于闭包)
关于更多Module模式的介绍,请访问个人上一篇文章:深刻理解JavaScript系列2:揭秘命名函数表达式
但愿上面的一些例子,能让你对当即调用的函数表达(也就是咱们所说的自执行函数)有所了解,若是你想了解更多关于function和Module模式的信息,请继续访问下面列出的网站:
ECMA-262-3 in detail. Chapter 5\. Functions. - Dmitry A. Soshnikov
Functions and function scope - Mozilla Developer Network
Named function expressions - Juriy “kangax” Zaytsev
深刻理解JavaScript系列3:全面解析Module模式 - hiyangguo
Closures explained with JavaScript - Nick Morgan
【深刻理解JavaScript系列】文章,包括了原创,翻译,转载,整理等各种型文章,原文是TOM大叔的一个很是不错的专题,现将其从新整理发布。谢谢大叔。若是你以为本文不错,请帮忙点个推荐,支持一把,感激涕零。
更多优秀文章欢迎关注个人专栏