當即執行函數,就是在定義函數的時候直接執行,這裏不是申明函數而是一個函數表達式javascript
1.問題java
在javascript中,每個函數在被調用的時候都會建立一個執行上下文,在函數內部定義的變量和函數只能在該函數內部調用,正是由於這個上下文,使得在調用函數的時候能夠建立一些私有變量。以下代碼es6
//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 )
依然會報錯,爲何呢?在一個函數聲明語句(此次是正確的)後面加上一對圓括號,這對圓括號和前面的聲明語句沒有任關係,而只是一個分組操做符,用來控制運算的優先級,這裏的意思是小括號裏面優先計算,因此上面代碼等同於:spa
function foo(){ counter1(); } (); // SyntaxError: Unexpected token )
2.概念code
正確的寫法是怎樣的呢?簡單,以下:
(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的屬性。
好了,就這麼多。