在JS中,若是咱們定義了一個函數以下:javascript
function fn(){ /* code */ }
複製代碼
或者java
let fn = function(){ /* code */ }
複製代碼
當咱們在調用時,都須要在後面加上一對圓括號,像這樣:fn()
安全
正如上面所寫的那樣,fn
相對於函數表達式function(){ /* code */ }
只是引用的變量
那麼咱們能夠能夠這樣寫嗎?bash
function(){ /* code */ }()
複製代碼
若是這樣的話,是會報錯的,由於當圓括號爲了調用函數而出如今函數後面時,不管在全局環境或者局部環境裏遇到了這樣的function
關鍵字。
默認的,他會將他看成是一個函數聲明,而不是函數表達式,若是你不明確的告訴圓括號他是一個表達式,他會將其看成沒有名字的函數聲明而且拋出一個錯誤,由於函數聲明須要一個名字。閉包
那麼若是咱們加上函數名呢?ide
function fn(){ /* code */ }()
複製代碼
依然報錯,由於這對圓括號和前面的聲明語句沒有任關係,而只是一個分組操做符,用來控制運算的優先級,這裏的意思是小括號裏面優先計算,因此上面代碼等同於:模塊化
function fn(){ /* code */ }
()
複製代碼
當咱們聲明瞭一個函數,可能不須要調用屢次,而且能夠只調用一次獲得一個單一的值函數
顯然上面的方法是不行的,那麼怎麼辦呢?其實咱們能夠這樣寫:post
(function () { /* code */ }());
複製代碼
或者這樣測試
(function () { /* code */ })();
複製代碼
那麼這二者又有什麼區別呢?
其實這二者形式就是最開始寫的那兩種函數的形式:
function fn(){ /* code */ }
let fn = function(){ /* code */ }
函數表達式後面能夠加括號當即調用該函數,函數聲明不能夠,只能以 fn() 形式調用
因此咱們能夠這樣寫當即執行函數
(function () { /* code */ }());
(function () { /* code */ })();
let i = function(){ /* code */ }();
let j = (function(){ /* code */ }());
true && function () { /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
new function(){ /* code */ };
new function(){ /* code */ }(); // 帶參數
複製代碼
能夠看到,在 function 前面加 ! 、+ 、- 甚至是逗號等均可以起到當即執行的效果,而 () 、! 、+ 、- 、= 等運算符,都將函數聲明轉換成函數表達式,消除了 javascript 引擎識別函數表達式和函數聲明的歧義,告訴 javascript 引擎這是一個函數表達式,不是函數聲明,能夠在後面加括號,並當即執行函數的代碼。
其實加括號是最安全的作法,由於 ! 、+ 、- 等運算符還會和函數的返回值進行運算,有時形成沒必要要的麻煩。
說到當即執行函數的話,順便扯一下閉包
和普通函數傳參同樣,當即執行函數也能夠傳遞參數。若是在函數內部定一個函數,而裏面的那個函數能引用外部的變量和參數(閉包),咱們就能用當即執行函數鎖定變量保存狀態。
下面用代碼來作測試:
<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
具體的使用,請參考個人文章:
《JavaScript 變量的使用》
《ES6 新增內容總結》
當即執行函數在模塊化的時候也有用,用當即執行函數處理模塊能夠減小全局變量形成的空間污染,而是使用私有變量。
以下建立一個當即執行的匿名函數,該函數返回一個對象,包含要暴露給外部的屬性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;
}
}
}());
counter.get();//0
counter.set(3);
counter.increment();//4
counter.increment();//5
conuter.i;//undefined (`i` is not a property of the returned object)
i;//ReferenceError: i is not defined (it only exists inside the closure)
複製代碼
這裏若是使用counter.i
來訪問這個內部變量,會報錯undefined
,由於i
並非counter
的屬性。
模塊模式方法不只至關的厲害並且簡單。很是少的代碼,你能夠有效的利用與方法和屬性相關的命名,在一個對象裏,組織所有的模塊代碼,即最小化了全局變量的污染也創造了使用變量。