在javaScript中,每一个函数被调用时,都会创建一个新的运行上下文。由于在一个函数里面定义的变量和函数仅仅能在里面訪问。在外面是不行的。上下文提供了一种非常easy的方法来创建私有性。
//makeCounter函数返回另外一个匿名函数,这个匿名函数可以訪问到“私有”变量i, 好像有一点“特权”性。 function makeCounter() { // i仅仅能在makeCounter的里面被訪问到 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 i; // ReferenceError: i没有被定义(它仅仅存在于makeCounter的内部)
在非常多情况下,你不须要函数返回多个“实例”。你只须要一个单例。又或者在其它情况下,你可能连一个返回值都不须要。
问题的关键
如今你定义了像function foo(){}或var foo = function(){}这种函数,这样你得到了一个函数的标识符。你能够通过()来调用它。如foo()。
//像这样定义的函数能够通过后面的()来调用,如foo(),由于foo不过一个 //函数表达式‘function(){/*...*/}‘的引用 var foo = function(){ /* ... */ } //只能过后面的() ,函数表达式就能被运行,这是不合常理的。 function(){ /* ... */ }(); // SyntaxError: Unexpected token (
正如你以上所看见的,出现了异常。
当JS解释器在全局作用域或函数的里面遇到functionkeyword时,会默觉得是一个函数声明,而不是一个函数表达式。假设你不确切地告诉解释器是一个表达式,它会觉得是一个没有名字的函数声明,所以导致语法错误异常,由于函数声明是须要名字的。
顺便说一下:函数、括号、语法错误
有趣的是,假设你为函数指定了一个名字,而且把括号放在它后面,出于不同的原因。解析器也会抛出一个语法错误。当括号放在表达式的的后面意味着表达式是一个将被调用的函数,括号放在声明的后面,会与前面的声明全然分开。只代表一个分组操作符(作为一种控制优先级的一种控制方式)。
<span style="font-weight: normal;">//这个函数在语法声明上是有效的。它不过一个声明,紧跟着的括号是无效的,
//由于分组操作符须要包含一个表达式
function foo(){ /* ... */ }(); // SyntaxError: Unexpected token )
//如今假设你在括号里放一个表达式,没有异常抛出
//可是函数仍然没有运行
function foo(){ /* ... */ }( 1 );
//事实上上面的相当于下面两个:一个是函数声明,一个是不相关的表达式
function foo(){ /* ... */ }
( 1 )</span>
想要了解很多其它。请訪问, ECMA-262-3
in detail. Chapter 5. Functions
马上调用表函数达式(IIFE)
幸运的是以上语法错误能够简单地被修复。最广泛的做法是告诉解析器函数表达式不过用括号括起来的,由于在javaScript中,括号不能包含声明。依据这一点,当解析器遇到functionkeyword时,会把它解析成一个表达式而不是函数声明。
//下面两种都能够被用来马上运行一个函数表达式,用函数的运行上下文去创建 //私有性 (function(){ /* ... */ }()); // Crockford推荐使用这个 (function(){ /* ... */ })(); // 可是这一个也工作得非常好 //从括号或强制操作符来看,消除了函数声明和函数表达式的歧义, //当解析器已经预期是一个表达式时,他们也能够被忽略,请看下面的样例 var i = function(){ return 10; }(); true && function(){ /* ...*/ }(); 0, function(){ /* ... */ }(); //假设你不关心函数的返回值或你的代码可读性 //你能够节省一个字节。通过在函数前面加一个一元操作符 !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); //还有另外的版本号,但我不清楚它的实现性能会如何,用‘new‘keyword实现。 //运行是正常的 new function(){ /* code */ } new function(){ /* code */ }()
关于括号须要注意的重要点
特别在“消除歧义”的括号(即包着函数表达式的括号),是不须要的(由于解析器已经觉得它是表达式了)。对于用在赋值中还是一个不错的想法。
这种括号非常明显暗示了函数表达式能够马上被调用,而且这个变量将包含函数的返回结果。不是函数本身。这能够免去一些人阅读你代码的麻烦,由于假设你的函数声明非常长时。可能要滚动到以下才知道你这个函数有没有被调用。
一般来说。想写出清晰的代码。从技术上说要阻止解析器抛出语法错误异常,编写清晰的代码也须要阻止其它开发人员把错误异常抛向你。
用闭包来保存状态
通过传递參数来调用马上函数。调用具有名称标识的函数并同一时候向它传递參数,这两者的调用方式是非常像的。
不论什么定义在一个函数里面的函数都能訪问它外面函数的所传递进来的參数以及外层函数的变量(这样的关系被称为闭包),一个马上调用函数表达式能够被用来“锁住”值和保存状态值。
假设你想学习很多其它关于闭包的内容,请阅读Closures explained with JavaScript.
以下例2和例3会依照你期望的运行,马上运行函数表达式有一个非常大的缺点是它是匿名或没有命名的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <a href="###">点我</a> <a href="###">点我</a> <a href="###">点我</a> <a href="###">点我</a> <a href="###">点我</a> <script type="text/javascript"> var elems = document.getElementsByTagName( ‘a‘ ); //例1 for ( var i = 0; i < elems.length; i++ ) { console.log(i); elems[ i ].addEventListener( ‘click‘, function(e){ e.preventDefault(); alert( ‘I am link #‘ + i ); }, ‘false‘ ); } //运行结果: //I am link #5 //I am link #5 //I am link #5 //I am link #5 //I am link #5 //例2 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 am link #0 //I am link #1 //I am link #2 //I am link #3 //I am link #4 //例3 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‘ ); } //运行结果: //I am link #0 //I am link #1 //I am link #2 //I am link #3 //I am link #4 </script> </body> </html>