Function 實例




在JavaScript中,函式是物件,是Function的實例。因為是Function實例,你可以將之傳給另一個變數參考。例如:
js> function max(num1, num2) {
  >     return num1 > num2 ? num1 : num2;
  > }
js> var maximum = max;
js> max(10, 20);
20
js> maximum(10, 20);
20
js>


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


上面你所看到的函式撰寫方式,稱之為函式實字(Function literal),這就像你寫下一個數值實字、物件實字或陣列實字,會產生數值或物件等:
var number = 10;
var obj = { x : 10 };
var array = [1, 2, 3];
var func = function() {
    // do something...
};

函式實字會產生Function實例,在JavaScript中,無論是函式宣告或函式實字,都會產生Function實例。事實上,你也可以直接指定建立Function實例:
js> var max = new Function('num1', 'num2', 'return num1 > num2 ? num1 : num2');
js> max(5, 6);
6
js>


基本上,實務上很少會直接建立Function實例,以上只是表示,函式確實是Function實例。

即然函式是物件,它就可以任意指定給其它變數,也就可以指定作為另一個函式的引數,那它就不僅能被呼叫,還可以主動要求另一個函式執行所指定的函式內容。例如:
js> var printIt = function(element) {
  >     print(element);
  > };
js> [1, 2, 3].forEach(printIt);
1
2
3
js> var comparator = function(num1, num2) {
  >     return num1 - num2;
  > };
js> [5, 1, 7, 3, 2].sort(comparator);
1,2,3,5,7
js>


上 例以Array為例,forEach可以對陣列的每個元素作「某些事」,「某些事」是由你使用函式來指定,陣列會逐一將元素傳入給你指定的函式作為引數。 sort則可以進行排序,但兩個元素的大小關係要由你告知,傳回正值表示傳入的num1順序上大於num2,要排在num2的後面,傳回0表示兩個順序相 同,傳回負值表示num1順序上小於num2,要排在num2的前面。

像這種將函式主動丟入函式作為引數,在JavaScript中是很常見到的應用。事實上,若不需要名稱,你也可以如下:
js> [1, 2, 3].forEach(function(element) {
  >     print(element);
  > });
1
2
3
js> [5, 1, 7, 3, 2].sort(function(num1, num2) {
  >     return num1 - num2;
  > });
1,2,3,5,7
js>


你也可以從函式中傳回函式,這通常會形成閉包(Closure)綁定某些運算過後的資源,再傳回函式,這之後還會再談到應用。

以函式實字所建立的Function實例,在指定給別的變數前,稱為所謂的匿名函式(Anonymous function)。你可以完全不使用名稱來執行函式:
js> (function() {
  >     print('anonymous function...');
  > })();
anonymous function...
js>


實際上,函式實字也可以指定名稱。例如:
js> var maximum = function max(num1, num2) {
  >     return num1 > num2 ? num1 : num2;
  > };
js> maximum(10, 20);
20
js> max(10, 20);
js: "<stdin>", line 6: uncaught JavaScript runtime exception: ReferenceError: "max" is not defined.
        at <stdin>:6

js>


上例中,函式實字所建立的max名稱,似乎不能使用,事實上,這種語法適用於使用函式實字建立Function實例,但又需遞迴的場合。例如:
js> var gcd = function g(num1, num2) {
  >     return num2 != 0 ? g(num2, num1 % num2) : num1;
  > };
js> gcd(10, 5);
5
js>


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



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


函式也可以擁有方法,這個之後再談,你也可以在其上新增特性或方法,就如同一個普通的物件。

函式宣告與函式實字在運用上,幾乎是相同的,但還是有細微的差別。例如,你若寫一個f.js檔案如下:
  • f.js
func();
function func() {
print('func');
}

直接使用Rhino直譯器載入執行的話,可以正常執行:
your_workspace> java org.mozilla.javascript.tools.shell.Main f.js
func

但如果你的f.js如下:
  • f.js
func();
var func = function() {
print('func');
};

你會得到錯誤的訊息:
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實例的方式,則每次都會針對你引數的字串再作直譯動作。
var max = new Function('num1', 'num2', 'return num1 > num2 ? num1 : num2');