簡介

JavaScript 的 function 主要特色有二:

  1. function 是 first-class object,可以像 value 一樣當作參數傳遞,也可以擴充其 property 及 method
  2. function 提供了 scope 的概念,在 function 中用 var 宣告變數皆會是 local variable(不同於某些語言,JavaScript 並不擁有 curly brace local scope)

Function Expression

又稱為 anonymous function,如下例:

1
2
3
var add = function (a, b) {
return a + b;
};

function expression 的一個特例為 named function expression,如下:

1
2
3
var add = function add(a, b) {
return a + b;
};

兩者的差異是,前者 function object 的 name property 會是空字串(Firefox & WebKit)或 undefined(IE),後者 add.name 的值會是 “add”。
註:name property 並非 ECMAScript 標準,但大多數環境皆可使用,特別在使用 debug 工具時可能常會用到。

另外,named function expression 不建議 assign 給另一個名稱的變數,如:var foo = function bar() {};,此用法在部分 browser(如 IE)並未被正確實作。

我們應避免使用如同 var add = new Function('a, b', 'return a + b;'); 的 constructor function,因為這種做法常會需要使用 escape character 且縮排不便,對可讀性影響很大。

Function Declaration

1
2
3
function add(a, b) {
return a + b;
}

上述兩種定義 function 的方式在結尾部分有所不同,expression 需加上分號, declaration 則不用。除此之外,最大的差異在於 hoisting 行為…

Function Hoisting

function declaration 與 named function expression 乍看之下兩者的外觀與行為都十分接近,其實不然。如同第2章筆記提到的 hoisting 行為,不論在程式何處宣告變數,都會在幕後被 hoisted 到最頂端(僅宣告該變數,不會定義其內容,即 var foo = undefined;)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// global functions
function foo() {
alert('global foo');
}
function bar() {
alert('global bar');
}
function hoistMe() {
console.log(typeof foo); // "function"
console.log(typeof bar); // "undefined"
foo(); // "local foo"
bar(); // TypeError: bar is not a function
// function declaration: 宣告及實作都會被 hoisted
function foo() {
alert('local foo');
}
// function expression: 只有變數宣告被 hoisted
var bar = function () {
alert('local bar');
};
}
hoistMe();

從上例可以很明顯看出:由於 hoisting 行為,named function expression 只有變數宣告被 hoisted,其 function 實作的部分並沒有;而 function declaration 則會將宣告及實作皆 hoisted。

Callback Function

設計 function 時應盡可能使其保持 generic,亦即不要將許多功能的實作寫死在同一個 function 中。這種需 coupling 多個動作於同一個 function 的動機,便是非常適合使用 callback 的時機。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var findNodes = function (callback) {
var i = 1000,
nodes = [],
found;
while (i--) {
// 做完複雜操作後,決定 found,並執行 callback
if (typeof callback !== "function") {
callback(found);
}
nodes.push(found);
}
return nodes;
};
var hide = function (node) {
node.style.display = "none";
};
findNodes(hide);

注意 callback 的 scope

當 callback 並非 anonymous function 或 global function,而是某個 object 的 method 時,如果其中有使用 this 去 refer 其所屬的 object,會導致非預期的行為。

解法:除了傳遞 callback 之外,額外傳遞其所屬 object,並將呼叫方式改為 callback.call(callback_obj, found);

其他使用 callback 的例子

  • Asynchronous Event Listener: document.addEventListener("click", callback, false);
  • Timeout: setTimeout(callback, 1000);

Returning Function

1
2
3
4
5
6
7
8
9
10
11
var init = function () {
var count = 0;
return function () {
return (count += 1);
};
};
var count = init();
count(); // 1
count(); // 2
count(); // 3

init() 中包裝 returned function 會產生一個 clusure,可用於儲存 private 資料,因為這些資料只能被 returned function 存取。

Self-Defining Function

1
2
3
4
5
6
7
8
9
var test = function () {
console.log("test");
test = function () {
console.log("new test");
};
};
test(); // "test"
test(); // "new test"

又稱 lazy function definition,用這種方式可回收舊的 function pointer 並指向新 function,即用新的實作內容覆蓋掉自己。對於某些只需執行第一次便不再用到的工作,此法非常適合使用。

Immediate Function

1
2
3
(function () {
console.log("Go!");
}());

又稱 self-invoking function 或 self-executing function,此語法可讓 function 在定義時立刻執行,適用於只需執行一次且不再重複使用時(如初始化動作),也可以避免暫時使用的變數汙染了 global scope。

immediate function 也可以有參數及回傳值:可將 global object 作為參數傳進 function 中以便存取 global variable;也可以在完成某個複雜的運算後回傳其值並將整個 function 直接 assign 給變數。

另外一個與此 pattern 類似的是 Immediate Object Initialization,適用在初始化較繁複的一次性工作,在 init() 完成後不再存取該 object。

1
2
3
4
5
6
7
({
num: 100,
init: function () {
// 初始化工作
// 如果執行一次後未來仍可能會存取此 object,可加上 return this;
}
}).init();

Function Property

由於 function 是 object,因此當然可以擁有 property。我們可以透過自訂 property 來作為運算結果的 cache,下次再被呼叫時便不必重複可能非常繁重的計算。

1
2
3
4
5
6
7
8
9
var myFunc = function (param) {
if (!myFunc.cache[param]) {
var result = {};
// 繁重的計算
myFunc.cache[param] = result;
}
return myFunc.cache[param];
};
// 可透過 myFunc.cache 直接存取計算過的結果

Configuration Object

此 pattern 在提供乾淨的 APIs 時相當有用。程式開發時,需求發生變動是很平常的事,因此可能會出現一些 function 逐漸擁有許多參數,且有些參數是 required 有些則是 optional。

1
2
function addShape(shape, owner, color, size, maker) {...}
addShape("circle", "Dylan", "blue", null, "Tom");

我們可以將所有參數替換成一個 object 並作為唯一的參數,這麼做相較於上例擁有下列優點:

  • 不需記住參數的順序
  • 可以安全的略過 optional 參數
  • 容易閱讀及維護
  • 容易新增及移除參數
1
2
3
4
5
6
var conf = {
shape: "circle",
owner: "Dylan",
color: "blue"
};
addShape(conf);

Curring

即 partial function application,當經常呼叫某個 function 且其傳入的參數大多都相同時,便適合使用 curring 技巧。使用時會先傳入部分參數給 function,並動態產生另一個新 function,如此該新 function 便能保留重複的那些參數,於是就不必每次都傳遞。

術語說明

  • curring:其命名是來自數學家 Haskell Curry,指 function transformations 的過程
  • unction application:實際上 function 並不是被 called 或 invoked,而是 applied。JavaScript 的 function object 有提供 Function.prototype.apply()Function.prototype.call() 兩個 method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var test = function (str) {
return "str: " + str;
};
test("LOL"); // invoke, "str: LOL"
test.apply(null, ["XD"]); // apply, "str: XD"
test.call(null, "XD"); // call, "str: XD"
var obj = {
test: function (str) {
return "str: " + str;
}
};
obj.test("LOL"); // "str: LOL"
test.apply(obj, ["XD"]); // "str: XD"
test.call(obj, "XD"); // "str: XD"

實際範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function add(x, y) {
if (typeof y === "undefined") { // partial
return function (y) {
return x + y;
};
}
// full application
return x + y;
}
typeof add(8); // "function"
add(4)(8); // 12
var ROC2AD = add(1911);
ROC2AD(102); // 2013

附註:書上另有 general-purpose function 可用,實作方式較為複雜,需要時可再查閱。