立即执行函数,就是在定义函数的时候直接执行,这里不是申明函数而是一个函数表达式
1.问题
在javascript中,每一个函数在被调用的时候都会创建一个执行上下文,在函数内部定义的变量和函数只能在该函数内部调用,正是因为这个上下文,使得在调用函数的时候可以创建一些私有变量。如下代码
//makeCounter,返回一个新的函数(闭包),这个函数可以访问makeCounter里的局部变量i function makeCounter() { var i = 0; return function () { document.write(++i); document.write('<br>'); } } //counter1和counter2是不同的实例,分别拥有自己范围内的变量i var counter1 = makeCounter(); counter1(); counter1(); var counter2 = makeCounter(); counter2(); counter2();
这里i是函数makeCounter函数内的局部变量,所以定义的counter1和counter2都有自己的变量i,上面代码输出结果如下:
注意闭i始终保存在内存中,所以第二次调用的时候输出的是2。
普通情况下我们定义一个函数,然后在函数名字后面加上一对圆括号就可以直接调用它,能不能定义完之后直接在后面加上小括号调用呢?如下
function(){ counter1(); }(); // SyntaxError: Unexpected token (
答案是不行,这样会报错的。为什么呢?在javascript解释代码的时候,遇到function关键字的时候就认为这里是一个函数声明,而不是函数表达式,如果没有显式地定义成函数表达式就会报错,因为函数声明需要一个函数名,上面的代码没有函数名。
既然是因为没有函数名字报错那好就加上一个函数名,如下:
function foo(){ counter1(); }(); // SyntaxError: Unexpected token )
依然会报错,为什么呢?在一个函数声明语句(这次是正确的)后面加上一对圆括号,这对圆括号和前面的声明语句没有任关系,而只是一个分组操作符,用来控制运算的优先级,这里的意思是小括号里面优先计算,所以上面代码等同于:
function foo(){ counter1(); } (); // SyntaxError: Unexpected token )
2.概念
正确的写法是怎样的呢?简单,如下:
(function () { counter1(); }());
这样为什么就可以呢?在javascript里圆括号内不能包含语句,当解释器对代码进行解释的时候遇到圆括号就认为这里面是表达式,然后遇到function关键字就认为这是一个函数表达式,而不是函数声明。而更加奇妙的是只要是能将后面语句预先解释为表达式都可以,不一定是分分组操作符,于是立即执行函数表达式有了五花八门的写法,如下:
(function () { counter1(); }()); (function () { counter1(); })(); var i = function(){ counter1(); }(); true && function () { counter1(); }(); 0, function(){ counter1() }(); !function () { counter1(); }(); ~function () { counter1(); }(); -function () { counter1(); }(); +function () { counter1(); }();
输出结果如下:
甚至可以这样:
new function(){ counter1(); } new function(){ counter1(); }() // 带参数
这样:
var i = function(){ counter1(); }(); var j = (function(){ return 10; }());
这是为什么呢?因为new,=是运算符,和+,-,*,/一个样,都会把后面的语句预先解释为表达式。这里推荐上面一种写法,因为function内部代码如果太多,我们不得不滚到最后去看function(){}后是否带有()。
3.立即执行函数和闭包有什么关系
和普通函数传参一样,立即执行函数也可以传递参数。如果在函数内部定一个函数,而里面的那个函数能引用外部的变量和参数(闭包),我们就能用立即执行函数坐定变量保存状态。
我们在hmtl页面中方两个超链接标签,然后用下面的代码来测试:
<div> <ul> <li><a>第一个超链接</a></li> <li><a>第二个超链接</a></li> </ul> </div> var elems = document.getElementsByTagName('a'); for(var i=0; i<elems.length; i++) { elems[i].addEventListener('click', function (e) { e.preventDefault(); alert('I am click Link #' + i); }, 'false') }
这段代码意图是点击第一个超链接提示“I am click Link #0”,点击第二个提示“I am click Link #1”。真的是这样吗? 不是,每一次都是“I am click Link #2”
因为i的值没有被锁住,当我们点击链接的时候其实for循环已经执行完了,于是在点击的时候i的值已经是elems.length了。
修改代码如下:
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 cliick Link #' + i); }, 'false') })(i) }
这次可以正确的输出结果,i的值被传给了LockedIndex,并且被锁定在内存中,尽管for循环之后i的值已经改变,但是立即执行函数内部的LockedIndex的值并不会改变。
还可以这样写:
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' ); }
但是我觉得如果用let是不是就可以一下子解决了:
var elems = document.getElementsByTagName('a'); for(let i=0; i<elems.length; i++) { elems[i].addEventListener('click', function (e) { e.preventDefault(); alert('I am click Link #' + i); }, 'false') }
let是块级作用域内的变量,是es6新定义的,这里不展开。
4.模块模式
立即执行函数在模块化的时候也有用,用立即执行函数处理模块可以减少全局变量造成的空间污染,而是使用私有变量。
如下创建一个立即执行的匿名函数,该函数返回一个对象,包含要暴露给外部的属性i,如果不实用立即执行函数就要多定义一个属性i了,这个i就会显示的暴露给外部,这样:counter.i,这种方式明显不太安全。
var counter = (function(){ var i = 0; return { get: function(){ return i; }, set: function( val ){ i = val; }, increment: function() { return ++i; } }; }()); document.write('<br>'); document.write(counter.get());document.write('<br>'); document.write(counter.set( 3 ));document.write('<br>'); document.write(counter.increment());document.write('<br>'); // 4 document.write(counter.increment());document.write('<br>'); // 5
注意,这里如果使用counter.i来访问这个内部变量,会报错undefined,因为i并不是counter的属性。
好了,就这么多。
作者:Tyler Ning
出处:http://www.cnblogs.com/tylerdonet/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,可以通过以下邮箱地址williamningdong@gmail.com 联系我,非常感谢。