Javascript:變量,靜態變量和this

Javascript的變量只有全局作用域和函數作用域,沒有其它語言中常見的塊作用域,也就是在()和{}作用域中的變量。變量從其聲明(var myVar)或首次賦值(此前未聲明)之處起開始處進入其生命期。有些文章認爲在Javascript函數中,變量即用即聲明是bad practice,因爲只要在函數中任意地方聲明瞭某個變量,該變量即在函數開頭處就進入了其生命期,因此best practice是前向聲明。但是下面代碼的運行結果(在firebug中)顯示變量仍然是從其聲明處進入生命期的。

    function sayHi2(){
    console.log(myVar); //this line will comlain myVar is not defined
    var myVar = 20;
    console.log("after:" + myVar);
    }
     
    sayHi2();

通過下面兩種方式產生一個全局作用域的變量:

1. 在任何函數體之外通過var關鍵字聲明的變量;

2. 在任何地方(函數體內或者函數體外),對一個從未聲明過的標識符賦值,從而使其成爲一個變量。

全局變量實際上是宿主對象的成員變量。比如在瀏覽器環境下,全局變量myVar實際上等於window.myVar。

構造函數中的變量

Javascript中的構造函數並沒有特別的形式和限定。一般程序員會將一個函數名的首字母置爲大寫,如果他想將該函數當作構造函數使用的話。下面是構造函數一例:

    var Person = function() {
    var a = 0; //聲明瞭一個局部變量,在構造函數外任何地方都無法使用它
    b = 1; //產生了一個全局變量
    this.c = 2; //產生了一個成員變量
    this.funcA = function(){console.log("funcA");}; //成員函數
    function funcB(){ //函數也是對象。因此,這個定義實際上聲明瞭一個局部變量,構造函數以外任何地方都無法引用它
    console.log("funcB");
    }
    };
     
    var p = new Person ();
    console.dir(p)

一般說來,構造函數只應該包含簡單的賦值和函數定義(注:函數定義一般應移出構造函數,並聲明在其prototype屬性上)。上例的目的是爲了演示函數中的變量作用域。
    function foo() {
    foo.counter = foo.counter || 0; // 將計數器初始化爲0
    foo.counter++;
     
    console.log(foo.counter);
    }
     
    for (var i = 0; i <=5; i++){
    foo();
    }

上述代碼運行結果(在firebug控制檯中)是列出了變量c和函數funcA。注意,上述示例中var a和函數funcB的聲明仍然可能是有意義的。它們可以用作構造函數中使用的輔助變量和輔助函數。

在C語言,以及c++在某些情況下,從函數中返回一個非基本類型的局部變量通常是不允許的。因爲c/c++是按值傳遞,當函數結束時,其堆棧被複位。基本類型(如int,char)其值可以直接按值傳遞出去,不會產生任何問題,但其它變量如果是按地址傳遞的話,其地址由於在堆棧中,因此該變量的數據會隨堆棧的復位而消亡。但在Javascript,Java和C#等語言中,這樣做是允許的。理論上它們仍然是傳值型語言,但由於它們傳遞的是變量的引用,而變量始終產生在堆上(沒有明確的語言規範和教程說明Javascript的變量位置),因此函數結束後,變量要麼被回收(沒有被引用的情況下),要麼繼續有效。

靜態變量

一衆語言都支持靜態變量,但遺憾的是Javascript並不支持。好在仍然有方法可以模擬出靜態變量。靜態變量的實質是它是函數作用域,但又不隨每次進入函數體而被初始化。由於Javascript中函數本身也是一種對象,因此可以這樣:

    function foo() {
    foo.counter = foo.counter || 0; // 將計數器初始化爲0
    foo.counter++;
     
    console.log(foo.counter);
    }
     
    for (var i = 0; i <=5; i++){
    foo();
    }

運行結果爲輸出1~6個數字。如果是匿名函數的話,可以用arguments.callee來代替函數名:

    function foo() {
    arguments.callee.counter = arguments.callee.counter || 0; // 將計數器初始化爲0
    arguments.callee.counter++;
     
    console.log(arguments.callee.counter);
    }
     
    for (var i = 0; i <=5; i++){
    foo();
    }
this變量

在函數(不包括構造函數)中使用this變量,this的值需要等到函數調用時,由其上下文環境確定。

在構造函數中使用this,其結果是引用到由構造函數通過new生成的那個對象上。

在字面量對象中定義的函數,this引用到字面量生成的對象上。

    var my = {
    init : function(){
    console.log(this);
    if (typeof this._done_ != 'undefined'){
    console.log("already inited.");
    }else{
    console.log("not inited.");
    this._done_ = true;
    }
    }
    }
    my.init();
    my.init();

第一次運行my.init()的結果顯示“not inited”,但第二次運行的結果就是”already inited.”。同時,結果顯示this爲一個Object,而非Window。因此,只要在字面量對象內聲明的函數,this都會始終綁定到當前的字面量對象上,無論是在:{}還是在其中的函數聲明中。但要注意,this無法傳遞。即如果將this傳遞給一個函數作爲參數,則在函數內部訪問到的this,並不一定是傳入的this值。

但是,值得注意的是,在字面量對象的屬性表達式中使用this,此時this並非引用到字面量對象,而是當前定義字面量的作用域對象上。比如:

    var my = {
    mem : "hello",
    msg : this.mem + " world!"
    };
    console.log(my.msg);

上例會顯示’undefined world’。這是因爲this引用到window對象上,而當前window對象中並無mem這一屬性。
在衆多的Javascript編程書籍中,沒有一本提到上面的例子,這不能不說是個遺憾。使用字面量對象來構建程序中的單例對象是一種較普通的設計模式,在定義某些變量時,不可避免地要用到其它變量。比如在定義環境配置時,常常會先定義一個home,再定義一些相對於該home的path。但是這裏沒有捷徑可走。下面的定義都會引起運行時錯誤:
var my= {
    home : "http://home",
    jsdir: my.home + "/js",
    init : function(){
        console.log("init");
    },

    start : function(){
        console.log("start");
        my.init();
    }
};

my.start();
錯誤的原因可能是因爲,上述語句作爲一個詞句執行,因此當爲jsDir/imgDir賦值時,對象my還沒有創建起來,因此還不能引用my.home。而使用this之所以錯誤的原因,則已經在前面講過了。然而,如果去掉第2行,則該代碼可以運行,儘管我們看到第8行也引用到了my.init();這是因爲,第8行只是定義,並非執行;而第2行時需要立即對my.home進行求值,所以會發現my沒有定義。這是在firebug中看到的行爲,是否有某些brower並非如此,待考。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章