2013年2月16日 星期六

[JS] function - 函數物件

前言

javascript引擎要執行javascript時,會先"預編譯",即對所有宣告的變數及函式進行處理,但要注意的是,在函數中被宣告的local變數只有在函式被呼叫時才會被處理,函數return之後這些變數就會被釋放。


函數的定義

函數有三種定義方式:
1. 以function宣告(ex. function abc(){})
2. 匿名函數(ex.function(){})
3. Function建構函數(ex. var fn = new Function("return true;"))
前面有提到在預編譯時會先處理被宣告的函數及變數,所以第一種的宣告方式或是將匿名函數指定給一個變數時....
var fn = function(){/*Do something*/};
會在預編譯時期先被處理,但在直接執行匿名函數....
(function(){/*Do something*/})();
或是用Function建構函數來建立函數時都會在執行到該行程式碼的時候才會被處理。因為這樣的特性使得我們能用Function來比較動態的宣告函數,但因為必須以字串方式傳入函式的敘述內容(函式中要執行的程式碼),所以相對不好維護,而且因為是動態編譯的,效率也會較差,我們可以用一段程式碼來證明:
var startTime = (new Date()).getTime();
for(var i=0; i<100000; i++){
    function fn(){}
}
var total = (new Date()).getTime() - startTime;
alert("function*100,000 takes "+total); // 62 secs

var startTime = (new Date()).getTime();
for(var i=0; i<100000; i++){
    new Function();
}
var total = (new Date()).getTime() - startTime; // 18991 secs
alert("new Function*100,000 takes "+total);


函數的作用域(scope)

就像上一段所說,因為宣告function會在預編譯時期就被處理,所以函數的作用域是取決於宣告時所在的位置,立即執行函式(IIFE,直接執行匿名函數)也是。但是以Function建構函式所建立的函數作用域是Global。
var a = 1;
function fn(){
    var a=2;
    var fn1 = function(){alert(a);}
    var fn2 = new Function("alert(a)");
    fn1(); //"2"
    fn2(); //"1"
}
fn();


arguments物件

在function中可以使用arguments這個array物件,其中包含了所有被傳入此函數的參數,即使這個函數並不需要任何的輸入,例:
function fn(){
    for(var i in arguments){alert(arguments[i]);}
}
fn(1,2,3,4,5,6,7,8,9);
順帶一提,要如何取得一個函數預定接收幾個參數呢?可以使用function.length


.call() & .apply()

之前剛好寫了一篇關於這兩個方法的文章
這兩個方法能讓function很靈活的視作不同object的屬性來使用,切換不同的scope,用完之後馬上消失,不會真的成為該object的屬性。


別忘了function物件也有屬性

function也是個物件,當然也有自己的屬性。下面的這段程式碼想用迴圈來依次輸出1~10,但這樣的寫法用到了全域變數,缺乏封閉性。
function fn(){
    return x++;
} 
var x = 1;
for(var i=1; i<=10; i++){
    alert(fn());
}
所以我們可以利用函數自己的屬性來暫存這個值
function fn(){
    return fn.x++;
} 
fn.x = 1;
for(var i=1; i<=10; i++){
    alert(fn());
}


閉包(Closure)

在C、JAVA等語言中,當呼叫函數並傳回後,所有的區域變數就會被釋放,即刪除他們所在的堆疊。但在Javascript裡,如果宣告了一個內嵌函數,區域變數將在函數傳回後仍能被存取。我們可以利用這種特性來設計具有封閉及包裹性的閉包架構:
var mod = (function(){
    var d=0;
    var foo=function(){return d;};
    return foo;
})();
alert(mod()); // "0",還是存取得到閉包中的變數
關於Javascript的模組化可看一下之前的這篇文章