2017年12月25日 星期一

[ JavaScript Gossip ] JavaScript 核心 : 一切都與函式有關 (Function 實例)

Source From Here 
前言 : 
在 JavaScript 中,函式是物件,是 Function 的實例. 因為是 Function 實例,你可以將之傳給另一個變數參考. 例如 : 
> function max(n1, n2) { return n1 > n2 ? n1: n2; }
> var maximum = max;
> max(10, 20);
20
> maximum(10, 20);
20

注意,在將 max 指定給 maximum 時,max 後並沒有加上 () 運算子,這表示要將 max 參考的物件指定給 maximum 參考(加上括號表示要執行函式)。將一個函式指定給變數,這看來如果覺得奇怪的話,或許下這個看來比較不奇怪 : 
> var maximum = function max(n1, n2){ return n1 > n2 ? n1:n2; }
> var maximum = max;
> max(1, 2);
2
> maximum(1, 2);
2

上面你所看到的函式撰寫方式,稱之為 函式實字Function literal),這就像你寫下一個數值實字、物件實字或陣列實字,會產生數值或物件等 : 
  1. var number = 10;  
  2. var obj = { x : 10 };  
  3. var array = [123];  
  4. var func = function() {  
  5.     // do something...  
  6. };  
Function 實例 介紹 : 
函式實字會產生 Function 的實例,在 JavaScript 中,無論是函式宣告或函式實字,都會產生 Function 的實例。事實上,你也可以直接指定建立 Function 的實例 : 
> var max = new Function('num1', 'num2', 'return num1 > num2 ? num1 : num2');
> max(5, 6);
6

基本上,實務上很少會直接建立 Function 實例,以上只是表示,函式確實是 Function 實例. 即然函式是物件,它就可以任意指定給其它變數,也就可以指定作為另一個函式的引數,那它就不僅能被呼叫,還可以主動要求另一個函式執行所指定的函式內容. 例如 : 
> var printIt = function(element){ console.log(element); }
> [1, 2, 3].forEach(printIt);
1
2
3

> var comparator = function(n1, n2) { return n1 - n2; }
> [5, 1, 7, 3, 2].sort(comparator);
1,2,3,5,7

上 例以 Array 為例,forEach 可以對陣列的每個元素作「某些事」,「某些事」是由你使用函式來指定,陣列會逐一將元素傳入給你指定的函式作為引數。 sort 則可以進行排序,但兩個元素的大小關係要由你告知,傳回正值表示傳入的 n1 順序上大於 n2,要排在 n2 的後面,傳回 0 表示兩個順序相 同,傳回負值表示 n1 順序上小於 n2,要排在 n2 的前面. 像這種將函式主動丟入函式作為引數,在 JavaScript 中是很常見到的應用。事實上,若不需要名稱,你也可以如下 : 
> [1, 2, 3].forEach(function(e){ console.log(e); })
1
2
3

> [5, 1, 7, 3, 2].sort(function(n1, n2){ return n1 - n2; })
1,2,3,5,7

你也可以從函式中傳回函式,這通常會形成閉包(Closure)綁定某些運算過後的資源,再傳回函式,這之後還會再談到應用. 以函式實字所建立的 Function 實例,在指定給別的變數前,稱為所謂的匿名函式Anonymous function)。你可以完全不使用名稱來執行函式 : 
> (function() {
> print('anonymous function...');
> })();

anonymous function...

實際上,函式實字也可以指定名稱. 例如 : 
> var maximum = function max(num1, num2) {
> return num1 > num2 ? num1 : num2;
> };

> maximum(10, 20);
20
> max(10, 20);
js: "", line 6: uncaught JavaScript runtime exception: ReferenceError: "max" is not defined.
at :6

上例中,函式實字所建立的 max 名稱,似乎不能使用,事實上,這種語法適用於使用函式實字建立 Function 實例,但又需遞迴的場合. 例如 : 
> var gcd = function g(n1, n2){ return n2!=0? g(n2, n1%n2):n1; } // 計算最大公因數
> gcd(10, 5);
5

在一個匿名函式中,如果想取得本身實例,可以藉由 arguments 的 callee 來取得. 例如 : 
> var gcd = function(num1, num2) {
> return num2 != 0 ? arguments.callee(num2, num1 % num2) : num1;
> };

> gcd(20, 10);
10

函式既然是物件,本身亦可擁有特性. 例如函式有個 length 特性,代表其參數個數 : 
> var gcd = function (n1, n2){ return n2!=0? arguments.callee(n2, n1%n2):n1; }
> gcd.length;
2

函式也可以擁有方法,這個之後再談,你也可以在其上新增特性或方法,就如同一個普通的物件. 函式宣告與函式宣告在運用上,幾乎是相同的,但還是有細微的差別。例如,你若寫一個 f.js 檔案如下 : 
  1. func();  
  2. function func() {  
  3.     print('func');  
  4. }  
直接使用 Rhino 直譯器載入執行的話或是 node js,可以正常執行 : 
your_workspace> java org.mozilla.javascript.tools.shell.Main f.js
func 

# node f.js
func

但如果你的f.js如下 : 
  1. func();  
  2. var func = function() {  
  3.     print('func');  
  4. };  
你會得到錯誤的訊息 : 
your_workspace> java org.mozilla.javascript.tools.shell.Main f.js
js: uncaught JavaScript runtime exception: TypeError: func is not a function, it is undefined.

錯誤訊息告訴你,func 值是undefined. 原因在於,直譯器在載入 .js 時,會先處理所有的宣告,包括變數與函式宣告,接著再執行程式. 所以在第一個 f.js 中,是以函式宣告方式,直譯器已處理完成,因此接下來再執行時,就可以找到func 所參考的函式。 而在第二個 f.js中,僅宣告了 func 變數,直譯器處理完這個宣告後,接下來要執行程式時,範圍中可以看到 func 變數,但此時還沒有指定值給 func,所以是 undefined,因此不能完成函式的執行. 

雖然不重要,但還是提一下的是,以上兩種方式,在遇到要建立函式實例時,都不會再重新直譯,但如果你以直接建構 Function 實例的方式,則每次都會針對你引數的字串再作直譯動作. 函式宣告方式所建立的函式名稱,其實也是範圍物件上的特性名稱. 例如若在全域範圍宣告 : 
> function func() { print('func'); }
> func();
func
> this.func();
func

可以看到,func 名稱是全域物件上的特性!

沒有留言:

張貼留言

[Linux 常見問題] What's the best way to send a signal to all members of a process group?

Source From  Here   Question   I want to  kill a whole process tree.  What is the best way to do this using any common scripting languages? ...