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)部的是局部變量)