立即执行函数
原文:http://web.jobbole.com/82520/
"立即执行函数"其实就是"立即执行函数表达式",这句话很重要,下面的文字就是描述为什么这么说。
函数表达式 var fn = funtion(){ /* code */ } 函数声明 function fn(){ /* code */ }
函数声明必须是一个单独的JavaScript语句。
1.介绍函数(函数声明,函数表达式)
在JavaScript中,每一个函数在被调用的时候都会创建一个执行上下文,在该函数内部定义的变量和函数只能在该函数内部使用,而正是因为这个上下文,让我们在调用函数的时候能创建一些私有变量。
(图1)
为什么函数one可以访问外部的i的值,可以参考:原文https://github.com/creeperyang/blog/issues/16
简单描述一下,函数上下文:每调用一次产生一个上下文,调用完毕后销毁。
在该上下文中,有个规律,里面的函数可以访问外部的函数的属性,而外部却不能访问函数的内部属性。在es6中就有块的概念了。
为啥有这个规律?
最小访问原则:
不允许你代码中所有的东西在任意地方都可用的好处是什么?其中一个优势,是作用域为你的代码提供了一个安全层级。计算机安全中,有个常规的原则是:用户只能访问他们当前需要的东西。
想想计算机管理员吧。他们在公司各个系统上拥有很多控制权,看起来甚至可以给予他们拥有全部权限的账号。假设你有一家公司,拥有三个管理员,他们都有系统的全部访问权限,并且一切运转正常。但是突然发生了一点意外,你的一个系统遭到恶意病毒攻击。现在你不知道这谁出的问题了吧?你这才意识到你应该只给他们基本用户的账号,并且只在需要时赋予他们完全的访问权。这能帮助你跟踪变化并记录每个人的操作。这叫做最小访问原则。眼熟吗?这个原则也应用于编程语言设计,在大多数编程语言(包括 JavaScript)中称为作用域。(引用http://web.jobbole.com/91134/)
回到问题的核心。
修改图(1)的代码.
// makeCounter函数返回的是一个新的函数,该函数对makeCounter里的局部变量i享有使用权 function makeCounter() { // i只是makeCounter函数内的局部变量 var i = 0; return function() { console.log( ++i ); }; } // 注意counter和counter2是不同的实例,它们分别拥有自己范围里的i变量 var counter = makeCounter(); counter(); // 1 counter(); // 2 var counter2 = makeCounter(); counter2(); // 1 counter2(); // 2 i; // 报错,i没有定义,它只是makeCounter内部的局部变量
那么,立即执行函数其实就是加一个小括号,那么
//列子1 function (){ /*code*/ }() //报错SyntaxError: Unexpected token (
在JavaScript代码解释时,当遇到function关键词时,会默认把他当成一个函数声明,而不是函数表达式,如果没有把它显视地表达成函数表达式,就报错了,因为函数声明需要一个函数名,而上面的代码中函数没有函数名。(以上代码,也正是在执行到第一个左括号(时报错,因为(前理论上是应该有个函数名的。)
加个函数名
//列子2 function foo(){ /*code*/ }() //依旧报错 SyntaxError: Unexpected token )
为什么会这样?在一个表达式后面加上括号,表示该表达式立即执行;而如果是在一个语句后面加上括号,该括号完全和之前的语句不搭嘎,而只是一个分组操作符,用来控制运算中的优先级(小括号里的先运算)。
如果没有优先级的话,这句话是对的,但是小括号的优先级比function的高,所以就会报错
//修改之后的代码 (function foo(){ /*code*/ }())
为什么这样就能立即执行并且不报错呢?因为在javascript里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
还有一些其他的骚操作:
// 这是一个自执行函数,函数内部执行的是自己,递归调用 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 */ }()); // 立即执行函数也可以自执行,不过不常用罢了 (function(){ arguments.callee(); }()); (function foo(){ foo(); }());