這裡介紹各種 JavaScript 函數的定義方式,有些方式很常見,但是有一些你可能沒看過。
以下是在 JavaScript 中四種建立函數的方式:
// 四種建立函數的方法 function declaration () {}; var funcExpression = function () {}; var namedFuncExpression = function named() {}; var fnConstructor = new Function ();
這些都是可以用來建立函數(Function)物件的方法,但是其中有些差異,以下我們將討論這些作法之間有什麼差別。
函數宣告是最常見的用法:
function fn () { // 函數內容 ... }
如果使用這樣的方式來定義函數,則在整個程式中同一個 scope 之內的任何地方都可以使用這個函數,就算在這個函數定義之前也沒問題,就像這樣:
// 呼叫 fn() fn(); function fn () { // 函數內容 ... }
函數在 JavaScript 是一個一級物件(first class object),所以你可以用 JavaScript 的運算式(expression)來建立函數,而這樣的運算式就稱為函數運算式(function expressions)。
用這種方式定義函數也很簡單,就把函數的宣告放在一般運算式的位置,這樣就可以建立一個函數了,例如:
// 定義 fnExpr() 函數 var fnExpr = function () { // 函數內容 ... };
除此之外,你也可以將這種方式應用在其他各種地方,例如放在括號中或是一元運算子之後:
// 匿名函數運算式(anonymous function expressions) (function fnExpr2() {} ); !function fnExpr3() {}
JavaScript 的函數運算式可分為具名與匿名兩種,具名函數運算式(named function expressions,簡稱 NFE)會在函數內部建立一個儲存自己名稱的變數,而這個變數在函數之外是看不到的:
var namedFuncExpression = function named() { return named.name; }; named(); // ReferenceError: named is not defined namedFuncExpression(); // 傳回 "named"
具名函數運算式對於在除錯時會非常有用,在 JavaScript 的除錯環境的 stack traces、call stacks 或 中斷點(breakpoints)列表中,如果碰到匿名函數大概只會顯示 anonymous 這樣沒有用處的名稱,如果是具名函數的話,就會清楚標示該函數的名稱,這對於除錯而言是很好用功能。
對於 JavaScript 的函數式程式設計(functional programming)而言(例如 currying 與 partials),函數運算式也是非常關鍵的功能之一。
最後一種方式就是直接使用 Function
這個關鍵字來建立函數物件,在使用時將參數與函數的內容依序傳入 Function
,然後就可以建立一個函數物件了。
這裡要注意的是,如果使用這樣的方式建立函數,不會建立任何的 closure,而在這個函數中只能存取全域(global)的變數或是存在於該函數內部的變數。
var add = new Function('a','b', 'return a + b'); add(1,2); // 輸出 3 var glbFoo = "global"; function scope() { var scpFoo = "scoped", scop=Function('console.log(typeof scpFoo)'), glob=Function('console.log(typeof glbFoo)'); scop(); glob(); } scope(); // 輸出 undefined // 輸出 string
另外使用這樣的方式所建立的函數,在每一次執行時都會進行 parse 的動作,所以效率會比較差。
如果以函數宣告的方式來建立函數,則這個函數會被提升(hoisted)到該 scope 的最頂端,所以可以讓整個 scope 都直接呼叫它,但如果是函數運算式就必須在函數定義之後,才可以使用:
declared(); // 輸出 declared function declared () { console.log('declared'); } expressed(); // TypeError: undefined is not a function var expressed = function () { console.log('expressed '); } expressed(); // 輸出 expressed
函數運算式與函數宣告在語法上很相似,很容易讓人混淆,在辨別它們時需要注意一下。根據 ECMAscript 得標準,函數宣告必須有一個識別名稱(identifier),所以說只要函數沒有識別名稱,那麼它就是一個函數運算式,這個很容易分辨,而接來的問題就是如何分辨具名的函數運算式與函數宣告。
由於函數宣告只容許作為指令稿或是函數的 sourceElements,它不能出現在巢狀的 blocks 中,因為 blocks 中只容許放置 statements,不能放置 sourceElements。
var a = 0; function b () {}; if (false) { // 巢狀結構,不是 source element var c = 0; function d () {}; } // 函數宣告 function foo() { // 另一個函數宣告 function bar() {}; if(true) { // 不是 source element,所以是函數運算式 function baz() {}; } // 因為放在一元運算子之後,會被視為運算式 // 所以這個是函數運算式 !function bng () {}; };
參考資料:CODEKRAFT