簡介
在前兩期的文章中,我們介紹了 node.js 工具與 JavaScript 的基本語法,文章連結如下:
在本期當中,我們將介紹 JavaScript 當中最有趣的一個領域,那就是「函數」(function) 的用法,還有「閉包」 (closure) 這種相當特別的概念。
函數的宣告
在 JavaScript 當中,函數的宣告方法大致有兩種,第一種的宣告方法就和一般程式語言 (C/C++, Python, Java) 等差不多,是採用 f(a,b,c…) 這種方式宣告的,但是必須在前面加上 function 這個關鍵字。在以下範例中, sub(a,b) 就是採用這種方式宣告的一個範例。
- function.js
- // 第一種寫法,直接宣告函數
- function sub(a, b)
- {
- return a - b;
- }
- // 第二種寫法,將匿名函數指定給變數。
- var add = function(a,b) {
- return a+b;
- }
- console.log("add(3,5)=", add(3,5), " sub(7,2)=", sub(7,2));
但是、在 JavaScript 當中,還有一種比較特別函數宣告方式,是在宣告了一個「匿名函數」之後,再把這個函數「塞給」一個變數。就像上述 `var add = function(a,b) …` 的做法,這樣我們就可以用 add(3,5) 這樣的方式去呼叫該函數了。
函數型態的參數
在上面的 add 範例中,我們將「函數」塞給一個變數,而且還可以直接把該變數當作函數來呼叫。那麼、我們能不能將函數當作參數來傳遞呢?關於這點、當然是可以的,以下是一個將「函數當作參數」的範例。
- fptr.js
- function moreThanFive(e) {
- return e > 5
- }
- function lessThanFive(e)
- {
- return e < 5
- }
- function filter(list, fun) {
- var nlist = []
- for(i = 0; i
- {
- if(fun(list[i]))
- {
- nlist.push(list[i])
- }
- }
- return nlist
- }
- list = [1,2,3,4,5,6,7,8,9,10]
- console.log("Filter(list, moreThanFive)="+filter(list, moreThanFive));
- console.log("Filter(list, lessThanFive)="+filter(list, lessThanFive));
上面的函數 filter 提供兩個參數, 第一個是陣列; 第二個則為函數返回 true/false 來決定要從陣列中取出的元素.
參數的存取
對於一般的函數,參數個數是固定的,例如上述範例的 filter(list, fun),明確的有兩個參數,因此直接用 list, fun 就可以存取該參數。但是、對於那種有不確定參數個數的函數,就沒有對應名稱可以用來存取這些參數了。還好,javascript 在呼叫每個函數時,都會將參數放到一個稱為 arguments 的變數裏,arguments 是一個類似陣列形態,我們可以透過 arguments 來存取每一個參數,以下是一個範例。
- arg.js
- function print() {
- for (var i in arguments) {
- console.log(i, ":", arguments[i]);
- }
- }
- print(3, 2.71828, "hello");
這種變動參數個數的函數,有時候很有用。例如、若我們要寫一個可以找出最小值的函數,就可以用下列的 min() 函數。
- min.js
- function min() {
- var m = arguments[0];
- for (var i in arguments) {
- if (arguments[i] < m)
- m = arguments[i];
- }
- return m;
- }
- var x = min(3, 7, 2, 9, 1, 5, 8);
- console.log("x=min(3, 7, 2, 9, 1, 5, 8)=", x);
變數的領域範圍
在上述的 min.js 程式中,您可以看到我們經常會用 var 這個關鍵字來宣告變數。但事實上,即使我們不用 var 宣告,該程式也能正常運作。以下是一個完全沒有 var 宣告的版本。
- min2.js
- function min() {
- m = arguments[0];
- for (i in arguments) {
- if (arguments[i] < m)
- m = arguments[i];
- }
- return m;
- }
- x = min(3, 7, 2, 9, 1, 5, 8);
- console.log("x=min(3, 7, 2, 9, 1, 5, 8)=", x);
閉包 (Closure)
對於很多 C/C++、Java、C#、VB 等語言的「程式人」而言,「閉包」是個很奇特而難以理解的概念,但對於 JavaScript、Lua、Python、Ruby 等動態語言來說,「閉包」卻是個很自然的用法,一點都不神秘。其實、是「閉包」 (Closure) 這個詞給人的感覺太深奧了,我們不需要迷惑於這個名詞的神秘感,請讓我們先來看一個範例。
- closure.js
- function greet(msg)
- {
- return function(name) {console.log(msg + ' ' + name)}
- }
- sayHi = greet('Hi')
- sayHi('John') // Show 'Hi John'
- sayHello = greet('Hello')
- sayHello('Peter') // Show 'Hello Peter'
函數 greet 返回另一個函數, 並且在返回函數中參考了外部變數 msg。 這種「把外層變數一起包進來」的機制,就稱為「閉包」。換句話說、只要直接在函數裏引用外層的變數,然後當我們將「函數封閉起來傳回」時,該函數仍然可以正常使用,這就是閉包的概念了。
結語
在本文中,我們介紹了 JavaScript 中的「函數、參數與閉包」等觀念,這些觀念在我們進行模組化或撰寫大型程式的時候,將會是非常重要的根基。JavaScript 當中的函數,可以被塞進變數裏,然後再將變數當作函數來呼叫。也可以放在參數裏,拿來傳遞給另一個函數使用,這種方式有點像 C 語言當中的函數指標,只是感覺更精簡,更有彈性而已。而那個感覺有點神祕的「閉包」觀念,也只不過是「在傳回一整個函數時、順便把外層的變數給包進來而已」,並不真的那麼神祕啊!
Supplement
* W3CSchools - JavaScript Functions
* W3CSchools - JavaScript Array Reference
* W3CSchools - JavaScript Closure
沒有留言:
張貼留言