2017年11月29日 星期三

[JS 文章收集] 用 Node.js 學 JavaScript 語言(3)函數、參數與閉包

Source From Here 
簡介 
在前兩期的文章中,我們介紹了 node.js 工具與 JavaScript 的基本語法,文章連結如下: 
* 用 Node.js 學 JavaScript 語言(1)簡介與安裝
* 用 Node.js 學 JavaScript 語言(2)基本控制

在本期當中,我們將介紹 JavaScript 當中最有趣的一個領域,那就是「函數」(function) 的用法,還有「閉包」 (closure) 這種相當特別的概念。 

函數的宣告 
在 JavaScript 當中,函數的宣告方法大致有兩種,第一種的宣告方法就和一般程式語言 (C/C++, Python, Java) 等差不多,是採用 f(a,b,c…) 這種方式宣告的,但是必須在前面加上 function 這個關鍵字。在以下範例中, sub(a,b) 就是採用這種方式宣告的一個範例。 
- function.js 
  1. // 第一種寫法,直接宣告函數  
  2. function sub(a, b)  
  3. {  
  4.     return a - b;  
  5. }  
  6.   
  7.   
  8. // 第二種寫法,將匿名函數指定給變數。  
  9. var add = function(a,b) {  
  10.     return a+b;  
  11. }  
  12. console.log("add(3,5)=", add(3,5), " sub(7,2)=", sub(7,2));  
執行結果: 
# node function.js
add(3,5)= 8 sub(7,2)= 5

但是、在 JavaScript 當中,還有一種比較特別函數宣告方式,是在宣告了一個「匿名函數」之後,再把這個函數「塞給」一個變數。就像上述 `var add = function(a,b) …` 的做法,這樣我們就可以用 add(3,5) 這樣的方式去呼叫該函數了。 

函數型態的參數 
在上面的 add 範例中,我們將「函數」塞給一個變數,而且還可以直接把該變數當作函數來呼叫。那麼、我們能不能將函數當作參數來傳遞呢?關於這點、當然是可以的,以下是一個將「函數當作參數」的範例。
- fptr.js
 
  1. function moreThanFive(e) {  
  2.   return e > 5  
  3. }  
  4.   
  5. function lessThanFive(e)  
  6. {  
  7.   return e < 5  
  8. }  
  9.   
  10. function filter(list, fun) {  
  11.   var nlist = []  
  12.   for(i = 0; i
  13.   {  
  14.     if(fun(list[i]))  
  15.     {  
  16.       nlist.push(list[i])  
  17.     }  
  18.   }  
  19.   return nlist  
  20. }  
  21.   
  22. list = [1,2,3,4,5,6,7,8,9,10]  
  23.   
  24. console.log("Filter(list, moreThanFive)="+filter(list, moreThanFive));  
  25. console.log("Filter(list, lessThanFive)="+filter(list, lessThanFive));  
執行結果: 
# node fptr.js
Filter(list, moreThanFive)=6,7,8,9,10
Filter(list, lessThanFive)=1,2,3,4

上面的函數 filter 提供兩個參數, 第一個是陣列; 第二個則為函數返回 true/false 來決定要從陣列中取出的元素. 

參數的存取 
對於一般的函數,參數個數是固定的,例如上述範例的 filter(list, fun),明確的有兩個參數,因此直接用 listfun 就可以存取該參數。但是、對於那種有不確定參數個數的函數,就沒有對應名稱可以用來存取這些參數了。還好,javascript 在呼叫每個函數時,都會將參數放到一個稱為 arguments 的變數裏,arguments 是一個類似陣列形態,我們可以透過 arguments 來存取每一個參數,以下是一個範例。 
- arg.js 
  1. function print() {  
  2.   for (var i in arguments) {  
  3.     console.log(i, ":", arguments[i]);  
  4.   }  
  5. }  
  6.   
  7. print(32.71828"hello");  
執行結果: 
# node arg.js
0 : 3
1 : 2.71828
2 : hello

這種變動參數個數的函數,有時候很有用。例如、若我們要寫一個可以找出最小值的函數,就可以用下列的 min() 函數。 
- min.js 
  1. function min() {  
  2.   var m = arguments[0];  
  3.   for (var i in arguments) {  
  4.     if (arguments[i] < m)  
  5.       m = arguments[i];  
  6.   }  
  7.   return m;  
  8. }  
  9.   
  10. var x = min(3729158);  
  11. console.log("x=min(3, 7, 2, 9, 1, 5, 8)=", x);  
執行結果: 
# node min.js
x=min(3, 7, 2, 9, 1, 5, 8)= 1

變數的領域範圍 
在上述的 min.js 程式中,您可以看到我們經常會用 var 這個關鍵字來宣告變數。但事實上,即使我們不用 var 宣告,該程式也能正常運作。以下是一個完全沒有 var 宣告的版本。 
- min2.js 
  1. function min() {  
  2.   m = arguments[0];  
  3.   for (i in arguments) {  
  4.     if (arguments[i] < m)  
  5.       m = arguments[i];  
  6.   }  
  7.   return m;  
  8. }  
  9.   
  10. x = min(3729158);  
  11. console.log("x=min(3, 7, 2, 9, 1, 5, 8)=", x);  
但是、上述這個沒有 var 的版本 min2.js ,與那個有 var 的版本其實在某些細微處有所不同,因為採用 var 宣告時,該變數將會是一個區域變數,而沒有採用 var 宣告就直接指定的方式,則會是一個「全域」變數,這種全域變數有可能造成更多的衝突問題,所以在一般的情況下,我們都會加上 var 宣告。 關於是否該為變數加上 var 的更詳細描述,可以參考下列文章: 
JavaScript 語言核心(3)你的變數 var 了嗎?

閉包 (Closure) 
對於很多 C/C++、Java、C#、VB 等語言的「程式人」而言,「閉包」是個很奇特而難以理解的概念,但對於 JavaScript、Lua、Python、Ruby 等動態語言來說,「閉包」卻是個很自然的用法,一點都不神秘。其實、是「閉包」 (Closure) 這個詞給人的感覺太深奧了,我們不需要迷惑於這個名詞的神秘感,請讓我們先來看一個範例。 
- closure.js 
  1. function greet(msg)  
  2. {  
  3.     return function(name) {console.log(msg + ' ' + name)}  
  4. }  
  5.   
  6. sayHi = greet('Hi')  
  7. sayHi('John')               // Show 'Hi John'  
  8. sayHello = greet('Hello')  
  9. sayHello('Peter')           // Show 'Hello Peter'  
執行結果: 
# node closure.js
Hi John
Hello Peter

函數 greet 返回另一個函數, 並且在返回函數中參考了外部變數 msg。 這種「把外層變數一起包進來」的機制,就稱為「閉包」。換句話說、只要直接在函數裏引用外層的變數,然後當我們將「函數封閉起來傳回」時,該函數仍然可以正常使用,這就是閉包的概念了。 

結語 
在本文中,我們介紹了 JavaScript 中的「函數、參數與閉包」等觀念,這些觀念在我們進行模組化或撰寫大型程式的時候,將會是非常重要的根基。JavaScript 當中的函數,可以被塞進變數裏,然後再將變數當作函數來呼叫。也可以放在參數裏,拿來傳遞給另一個函數使用,這種方式有點像 C 語言當中的函數指標,只是感覺更精簡,更有彈性而已。而那個感覺有點神祕的「閉包」觀念,也只不過是「在傳回一整個函數時、順便把外層的變數給包進來而已」,並不真的那麼神祕啊! 

Supplement 
W3CSchools - JavaScript Functions 
W3CSchools - JavaScript Array Reference 
W3CSchools - JavaScript Closure

沒有留言:

張貼留言

[ FP In Python ] Ch1. (Avoiding) Flow Control

Preface   In typical imperative Python programs—including those that make use of classes and methods to hold their imperative code—a block...