Skip to content

Latest commit

 

History

History
202 lines (124 loc) · 7.34 KB

Function.md

File metadata and controls

202 lines (124 loc) · 7.34 KB

函式(Function)

  • JavaScript的函式與物件一樣,都是第一級物件。
  • C、Java或類似語言是以{ }來提供作用域,JavaScript則是以function區塊提供作用域。

定義函式

調用函式

函式調用

方法調用

建構式調用

間接調用

函式的種類

ECMAScript中色含三類函式,每一類都有各自的特性。

函式宣告(Function Declaration)

函式宣告(簡稱FD)是指這樣的函式

  • 有函式名
  • 代碼位置在:要麼在程式級別或者直接在另外一個函式的函式體(Function Body)中
  • 在進入函式上下文時創建的
  • 會影響變數物件

函式宣告是以如下形式聲明的:

function exampleFunc() { ... }  //要注意的是,函式宣告的句尾無需加(;)

這類函式的主要特性是:

  • 只有它們可以影響變數物件(存儲在上下文的VO中)。

  • 它們在執行代碼階段就已經存在了(因為FD在進入上下文階段就收集到了VO中)。

  • 函式宣告在代碼中的位置:

      function globalFD() {     // 函式宣告可以直接在全域上下文中 
          function innerFD() {} // 或者在另外一個函式的函式體中 
      }
    

    除了上述提到了兩個位置,其他位置均不能出現函式宣告。

函式運算式(Function Expression)

函式運算式(簡稱:FE)是指這樣的函式:

  • 代碼位置必須是在運算式的位置
  • 函式名字可有可無
  • 不會影響變數物件
  • 在執行代碼階段時創建

這類函式的主要特性是:

  • 它們的程式碼總是在運算式的位置

      var foo = function () { ... };  //最簡單的運算式的例子,就是賦值運算式
    

上述程式碼將一個匿名FE賦值給了變數foo,之後該函式就可以通過foo被訪問了。

FE也可以有名字稱

var foo = function _foo() { ... };

有名稱的FE優點是,在FE的外部可以通過變數foo調用函式,而在函式內部(比如遞迴調用),還可以用_foo來調用函式。
要注意的是在foo函式外部是無法調用_foo的。

區分FD與有名字的FE

當FE有名字的時候,它很難與FD區分。不過,從定義還是可以區分它自,因為FE總是出現在運算式的位置

var foo = function () { ... };  // 等於後面只能是運算是
(function foo() {...});         // 在括弧中(grouping operator)只可能是運算式 
[function foo () {...}];        // 在陣列初始化中也只能是運算式 
1, function foo () {...};       // 逗號操作符後只能跟運算式 
!function foo () {...};         // 驚嘆號操作符也只能跟運算式
...

FE能帶來什麼好處?

使用函式運算式能避免對變數物件造成"污染"!
最簡單的例子就是將函數作為參數傳遞給另外一個函數

function foo(callback) { 
  callback(); 
} 
 
foo(function bar() { 
  ...
}); 
 
foo(function baz() { 
  ...
});

若將FE指定給變數,這樣變數就儲存對FE的引用,如此一來,函數就會保留在記憶體中,並在之後可以通過變數來訪問。

var foo = function _foo() { console.log("foo"); };
foo();   // output foo

宣告式 v.s. 表達式

到底該用函式宣告式還是函式表示式呢?

立即函式(Immediate Function)

故名思義,立即函式是"創建後就馬上執行的函式""。

如何創建立即函式

為什麼常在書中或其它地方看到,創建立即函式都有括弧包著函式?
從標準中來看,創建立即函式的規則是函式必須在表達式的位置並且調用它。
所以立即函式,它必須是FE,而不能是FD。
而創建運算式最簡單的方式就是使用上述提到的組操作符。因為在組操作符中只能是運算式。

表達式的一些語法限制

標準中提到,運算式語句(ExpressionStatement)不能以左大括弧{開始,因為這樣一來就和程式碼區塊衝突了,也不能以function關鍵字開始,因為這樣又和函式宣告衝突了。

function () { ... }();  
function foo() { ... }();

上述兩段程式碼都會拋出錯誤,只是原因不同。

  • 第一個例子中,解譯器會以函式宣告來處理,因為它看到了是以function開始的。既然是個函式宣告,則缺少函數名(一個函式宣告其名字是必須的)。

  • 第二個例子中,看上去已經有了名字(foo),應該會正確執行。然而,這裡還是會拋出語法錯誤,因為組操作符內部缺少運算式。這個例子中,函式宣告後面的()會被當組操作符來處理,而非函式調用的()。

      function foo(x) { console.log(x); }(1); // 這裡只是組操作符,並非調用 
      foo(10); // 這裡是調用, 10
    

    上述程式碼其實就是以下程式碼

      function foo(x) { console.log(x); } // function declaration 
      (1);                                // 含運算式的組操作符 
      (function () {});                   // 另外一個組操作符包含一個函數運算式
    

回到創建立即函式上,如之前所述

它必須要是個函數運算式,而不能是函式宣告。

而創建運算式最簡單的方式就是使用上述提到的組操作符,因為在組操作符中只能是運算式。
如此一來解譯器將會以FE的方式來處理,這樣的函數將在執行階段創建出來,然後馬上執行,隨後被移除。

(function foo(x) { 
	console.log(x); 
})(1);               //output 1, 這樣就是函式調用,而不再是組操作符了

在下面的例子中,其括弧就不再是必須的了,因為函數本來就在運算式的位置了,解譯器自然會以FE來處理,並且會在執行程式碼階段創建該函數

var foo = { 
	bar : function (x) { 
		return x ; 
	}(1) 
}; 
console.log(foo.bar);   // output 1

如果要在函式創建後馬上進行函式調用,但函數不在運算式的位置時,括弧就是必須的。
除了使用括弧的方式將函數轉換成為FE之外,還有其他的方式

1, function () { ... }(); 
!function () {... }(); 

當然還有很多方式可以創建立即函式,不過,括弧是最通用也是最優雅的方式。

同樣是立即函式的宣告,下列程式碼的差異在哪?

(function () {})();
!function () { ... }(); 
精簡

對於JavaScript,精簡是一種很重要的特性,因為在進入頁面後JavaScript會馬上被下載。如果JavaScript的程式碼能越小,頁面載入的時間也會變快。
比較下列程式碼

(function(){})() 16 characters
!function(){}() 15 characters

顯然,!function(){}() (function(){})()有更高的壓縮率。

否定的返回值
var a = (function(){})()
var b = !function(){}()

上述函式沒有返回值,變數a的值將會是undefined,由於!undefined的值為true,變數b會被設置成true
對於想要"否定返回值的函數"或"任何事物都必需返回非null或非undefined"的設計者來說,這是一項優點。

Object與Function

Javascript中有兩個特殊的物件:ObjectFunction,它們都可做為建構函式,用於創建物件。
在JavaScript中所有的物件都繼承自Object原型,而Function又充當了物件的建構函式,乍看之下有點難懂,下面用圖示來解示。

使用new關鍵字創建物件過程

以下部份將用程式碼來解釋物件創建過程