深入理解 函式、匿名函式、自執行匿名函式

Preference發表於2018-05-08

1.基礎概念:定義函式的方式

一般定義函式有兩種方式:

  1. 函式的宣告
  2. 函式表示式

1.1函式的宣告

如下方法 add 就是函式宣告的程式碼結構:

function add(x,y){  
   alert(x+y) 
}  
add(1,2) //彈窗顯示:3  
複製程式碼

關於函式宣告,它最重要的一個特徵就是函式宣告提升,意思是執行程式碼之前先讀取函式宣告。這意味著可以把函式宣告放在呼叫它的語句之後。如下程式碼可以正確執行:

add(1,2); //彈窗顯示:3  
function add(x,y){  
    alert(x+y) 
}  
複製程式碼

1.2函式表示式

函式表示式中有幾種不同的語法。最常見和最具代表性的一種如下所示:

var add = function(x,y){  
    alert(x+y) 
}
add(1,2) //彈窗顯示:3
複製程式碼

這種形式看起來好像是常規的變數賦值語句。但是函式表示式和函式宣告的區別在於,函式表示式在使用前必須先賦值。所以這段程式碼執行的時候就會出錯:

add(1,2) //無彈窗,報錯: add is not a function  
var add = function(x,y){  
    alert(x+y)  
} 
複製程式碼

造成這種現象是因為解析器在向執行環境中載入資料時,解析器會率先讀取函式宣告,並使其在執行任何程式碼前可用;至於函式表示式,則必須等到解析器執行到它的所在的的程式碼行,才會真正的被解析。函式表示式中,建立的函式叫做匿名函式,因為function關鍵字後面沒有識別符號。

2.匿名函式的呼叫方式

匿名函式,顧名思義就是沒有名字的函式。上面的函式表示式中的建立,實際上是建立一個匿名函式,並將匿名函式賦值給變數 add,用 add 來進行函式的呼叫,呼叫的方式就是在變數 add 後面加上一對括號(),如果有引數傳入的話就是 add(1,2),這就是匿名函式的一種呼叫方式。

還有一種匿名函式的呼叫方式是:使用()將匿名函式括起來,然後後面再加一對小括號(包含引數列表)。我們再看一下以下一個例子:

alert((function(x,y){return x+y;})(2,3)) //彈窗提示5  
alert((new Function("x","y","return x+y"))(2,3)) //彈窗顯示5  
複製程式碼

在javascript中,是沒有塊級作用域這種說法的,以上程式碼的這種方式就是模仿了塊級作用域(通常成為私有作用域),語法如下所示:

(function(){  
    //這裡是塊級作用域  
})();  
複製程式碼

以上程式碼定義並立即呼叫了一個匿名函式。經函式宣告包含在一對圓括號中,表示它實際上是一個函式表示式。而緊隨其後的另一對圓括號會立即呼叫這個函式。然而要注意一點:

function(){  
      
}();  
複製程式碼

上面的程式碼是錯誤的,因為Javascript將function關鍵字當作一個函式宣告的開始,而函式宣告後面不能加圓括號,如果你不顯示告訴編譯器,它會預設生成一個缺少名字的function,並且丟擲一個語法錯誤,因為function宣告需要一個名字。有趣的是,即便你為上面那個錯誤的程式碼加上一個名字,他也會提示語法錯誤,只不過和上面的原因不一樣。提示為:Uncaught SyntaxError: Unexpected token (

在一個表示式後面加上括號(),該表示式會立即執行,但是在一個語句後面加上括號(),是完全不一樣的意思,只是分組操作符。

function foo(){
   alert('測試是否彈窗')
}() 
// SyntaxError: Unexpected token ) 
// 報錯因為分組操作符需要包含表示式

function foo(){ 
    alert('測試是否彈窗')
}(1) 
// (1) => 等價於 1
// 相當於foo方法後面個跟了一個無關係的表示式子:(1)
複製程式碼

所以上面程式碼要是想要得到想要的彈窗提示,就必須要實現賦值,如

a = function(){
    alert('測試是否彈窗')
}()
// 彈窗提示成功
複製程式碼

"a=" 這個片段告訴了編譯器這個是一個函式表示式,而不是函式的宣告。因為函式表示式後面可以跟圓括號。

因此下面兩段程式碼是等價的

var aa = function(x){  
    alert(x)  
}(5) //彈窗顯示:5  
複製程式碼
(function(x){
    alert(x)
})(5) //彈窗顯示:5    
複製程式碼

從上面對於函式和匿名函式的瞭解,我們引申出來了一個概念,即自執行函式。那為什麼 a =function(){}() 這種表示方法可以讓編譯器認為這個是一個函式表示式而不是一個函式的宣告?

3.自執行匿名函式

自執行函式,即定義和呼叫合為一體。我們建立了一個匿名的函式,並立即執行它,由於外部無法引用它內部的變數,因此在執行完後很快就會被釋放,關鍵是這種機制不會汙染全域性物件。

下面我們來看下一些比較有趣的自執行函式表達方式:

// 下面2個括弧()都會立即執行  
(function () { /* code */ } ()) // 推薦使用這個  
(function () { /* code */ })() // 但是這個也是可以用的  
  
// 由於括弧()和JS的&&,異或,逗號等操作符是在函式表示式和函式宣告上消除歧義的  
// 所以一旦解析器知道其中一個已經是表示式了,其它的也都預設為表示式了  
var i = function () { return 10; } ();  
true && function () { /* code */ } ();  
0, function () { /* code */ } ();  
  
// 如果你不在意返回值,或者不怕難以閱讀
// 你甚至可以在function前面加一元操作符號  
!function () { /* code */ } ();  
~function () { /* code */ } ();  
-function () { /* code */ } ();  
+function () { /* code */ } ();  
  
// 還有一個情況,使用new關鍵字,也可以用,但我不確定它的效率  
// http://twitter.com/kuvos/status/18209252090847232  
new function () { /* code */ }  
new function () { /* code */ } () // 如果需要傳遞引數,只需要加上括弧()  
複製程式碼

4.總結

看到這裡相信大家對函式有了全新的認識,希望能夠幫助到大家。

文中若有寫的不對或需要改進的或者有不同的簡介歡迎回復一起探討。?

? 參考連結

相關文章