function makeCounter() {
// i只是makeCounter函數(shù)內(nèi)的局部變量
var i = 0;
return function () {
console.log(++i);
};
}
// counter和counter2是不同的實例,它們分別擁有自己范圍里的i變量
var counter = makeCounter();
counter(); // i:1
counter(); // i:2
counter(); // i:3
var counter2 = makeCounter();
counter2(); // i:1
counter2(); // i:2
console.log(i); // i is not defined
一種私有變量創(chuàng)建方式,也是閉包的應(yīng)用之一。
但是,很多時候,我們不需要函數(shù)聲明,不需要后續(xù)的在調(diào)用。
var makeCounter = (function() {
var i = 0;
return {
sum: function() {
i++;
},
sayI: function() {
console.log(i);
}
}
})();
makeCounter.sum();
makeCounter.sum();
makeCounter.sum();
makeCounter.sayI(); // 3
看這兩個例子:
var fn = function() {console.log(1)};
function() {console.log(2)}; // SyntaxError: Unexpected token (
第二個函數(shù)報錯了,這是為何?
原文解釋:在javascript代碼解釋時,當(dāng)遇到
function
關(guān)鍵字時,會默認(rèn)把它當(dāng)做是一個函數(shù)聲明,而不是函數(shù)表達(dá)式,如果沒有把它顯視地表達(dá)成函數(shù)表達(dá)式,就報錯了,因為函數(shù)聲明需要一個函數(shù)名,而上面的代碼中函數(shù)沒有函數(shù)名。以上代碼,也正是在執(zhí)行到第一個左括號(
時報錯,因為(
前理論上是應(yīng)該有個函數(shù)名的。
簡單直接,解析到function
關(guān)鍵字,是一個函數(shù)聲明,而函數(shù)聲明需要函數(shù)名。JS引擎認(rèn)為它“不完整”,所以報錯了。
那么我們加上函數(shù)名,并他們都立即執(zhí)行。
var fn = function() {console.log(1)}(); // 1
function fn2() {console.log(2)}(); // SyntaxError: Unexpected token )
還是報錯了,不過這次報錯的不是左括號,而是右括號。why?
在表達(dá)式后面加上()
表示該表達(dá)式立即執(zhí)行,在JS引擎逐行解釋代碼時,匿名函數(shù)就已聲明好,當(dāng)?shù)?code>function() {console.log(1)}(),解釋器就會默認(rèn)()
前的內(nèi)容為表達(dá)式,而不是語句。
實際情況是,第一個函數(shù)被識別為了表達(dá)式,第二個函數(shù)依舊是語句。
而對于第二個函數(shù),后面加括號等價于:
function fn2() {console.log(2)}
()
原文解釋:相當(dāng)于先聲明了一個叫
foo
的函數(shù),之后進(jìn)行()
內(nèi)的表達(dá)式運(yùn)算,但是()
(分組操作符)內(nèi)的表達(dá)式不能為空,所以報錯。(也就是執(zhí)行到右括號時,發(fā)現(xiàn)表達(dá)式為空,所以報錯)
因為()
和語句搭配時,()
只有一個意義:運(yùn)算中的優(yōu)先級(小括號里的先運(yùn)算)
那么對于第一個函數(shù),根據(jù)上面的結(jié)論,我們可知,var fn =
這一部分,神奇的將后面的函數(shù)語句轉(zhuǎn)化為了表達(dá)式,使得后面的括號有意義(作為表達(dá)式執(zhí)行)。
所以,如若想使第二個函數(shù)后面的括號有意義,那么我們必須將函數(shù)語句轉(zhuǎn)化為函數(shù)表達(dá)式。
( function fn2() {console.log(2)} )(); // 2
并且只要不加;
號(表示語句或表達(dá)式結(jié)束),還可以空行執(zhí)行。
(
function fn2() {console.log(2)}
)
(
)
當(dāng)然沒什么用。
我們也知道了,立即執(zhí)行函數(shù)表達(dá)式為什么有這么多種寫法:
// 最常用的兩種寫法
(function(){ /* code */ }()); // 推薦寫法
(function(){ /* code */ })(); // 當(dāng)然這種也可以
// 括號和JS的一些操作符(如 = && || ,等)可以在函數(shù)表達(dá)式和函數(shù)聲明上消除歧義
// 如下代碼中,解析器已經(jīng)知道一個是表達(dá)式了,于是也會把另一個默認(rèn)為表達(dá)式
// 但是兩者交換則會報錯
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
// 如果你不怕代碼晦澀難讀,也可以選擇一元運(yùn)算符
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
// 你也可以這樣
new function(){ /* code */ }
new function(){ /* code */ }() // 帶參數(shù)
- 無論何時,給立即執(zhí)行函數(shù)加上括號是個好習(xí)慣
通過以上的介紹,我們大概了解通過()可以使得一個函數(shù)表達(dá)式立即執(zhí)行。
有的時候,我們實際上不需要使用()使之變成一個函數(shù)表達(dá)式,啥意思?比如下面這行代碼,其實不加上()也不會保錯:
// 可以不加括號
var i = function(){ console.log(10) }();
// 但是推薦還是加上
var i = ( function(){console.log(10)}() );
匿名函數(shù)表達(dá)式+閉包
這個用一個經(jīng)典例子說明吧:
var nodes = document.querySelectorAll("a");
// alert出的都是3
for (var i=0;i<nodes.length;i++) {
nodes[i].addEventListener("click",function(e) {
e.preventDefault();
alert("i'am link #" + i);
})
}
// 正常
for (var i=0;i<nodes.length;i++) {
(function(num) {
nodes[i].addEventListener("click",function(e) {
e.preventDefault();
alert("i'am link #" + num);
})
})(i)
}
// 另一種改寫:
var fn = function(num) {
return function(e) {
e.preventDefault();
alert("i'am link #" + num);
}
};
for (var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener("click",fn(i),false)
}
// 但是無疑問,立即執(zhí)行的函數(shù)表達(dá)式可讀性更佳。
匿名函數(shù)表達(dá)式+遞歸
對于有函數(shù)名的函數(shù)表達(dá)式可以:
var count = 0;
function foo() {
console.log(count);
count++;
if (count === 10) {
return;
}
foo();
}
foo();
對于沒有函數(shù)名的函數(shù)表達(dá)式遞歸,需要借用arguments.callee
。
var count = 0;
(function () {
console.log(count);
count++;
if (count === 10) {
return;
}
arguments.callee();
})()
當(dāng)然ES5后禁止使用callee()方法。
警告:在嚴(yán)格模式下,第5版 ECMAScript (ES5) 禁止使用
arguments.callee()
。當(dāng)一個函數(shù)必須調(diào)用自身的時候, 避免使用arguments.callee()
。 通過要么給函數(shù)表達(dá)式一個名字,要么使用一個函數(shù)聲明。
————MDN
匿名函數(shù)表達(dá)式 or 模塊化
原文代碼:
// 創(chuàng)建一個立即執(zhí)行的匿名函數(shù)
// 該函數(shù)返回一個對象,包含你要暴露的屬性
// 如下代碼如果不使用立即執(zhí)行函數(shù),就會多一個屬性i
// 如果有了屬性i,我們就能調(diào)用counter.i改變i的值
// 對我們來說這種不確定的因素越少越好
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并不是counter的屬性
i; // ReferenceError: i is not defined (函數(shù)內(nèi)部的是局部變量)