Javascript的變量只有全局作用域和函數(shù)作用域,沒有其它語言中常見的塊作用域,也就是在()和{}作用域中的變量。
變量從其聲明(var myVar)或首次賦值(此前未聲明)之處起開始處進(jìn)入其生命期。有些文章認(rèn)為在Javascript函數(shù)中,變量即用即聲明是bad practice,因為只要在函數(shù)中任意地方聲明了某個變量,該變量即在函數(shù)開頭處就進(jìn)入了其生命期,因此best practice是前向聲明。但是下面代碼的運(yùn)行結(jié)果(在firebug中)顯示變量仍然是從其聲明處進(jìn)入生命期的。
- function sayHi2(){
- console.log(myVar); //this line will comlain myVar is not defined
- var myVar = 20;
- console.log("after:" + myVar);
- }
- sayHi2();
通過下面兩種方式產(chǎn)生一個全局作用域的變量:
1. 在任何函數(shù)體之外通過var關(guān)鍵字聲明的變量;
2. 在任何地方(函數(shù)體內(nèi)或者函數(shù)體外),對一個從未聲明過的標(biāo)識符賦值,從而使其成為一個變量。
全局變量實(shí)際上是宿主對象的成員變量。比如在瀏覽器環(huán)境下,全局變量myVar實(shí)際上等于window.myVar。
構(gòu)造函數(shù)中的變量
Javascript中的構(gòu)造函數(shù)并沒有特別的形式和限定。一般程序員會將一個函數(shù)名的首字母置為大寫,如果他想將該函數(shù)當(dāng)作構(gòu)造函數(shù)使用的話。下面是構(gòu)造函數(shù)一例:
- var Person = function() {
- var a = 0; //聲明了一個局部變量,在構(gòu)造函數(shù)外任何地方都無法使用它
- b = 1; //產(chǎn)生了一個全局變量
- this.c = 2; //產(chǎn)生了一個成員變量
- this.funcA = function(){console.log("funcA");}; //成員函數(shù)
- function funcB(){ //函數(shù)也是對象。因此,這個定義實(shí)際上聲明了一個局部變量,構(gòu)造函數(shù)以外任何地方都無法引用它
- console.log("funcB");
- }
- };
- var p = new Person ();
- console.dir(p);
一般說來,構(gòu)造函數(shù)只應(yīng)該包含簡單的賦值和函數(shù)定義(注:函數(shù)定義一般應(yīng)移出構(gòu)造函數(shù),并聲明在其prototype屬性上)。上例的目的是為了演示函數(shù)中的變量作用域。
- function foo() {
- foo.counter = foo.counter || 0; // 將計數(shù)器初始化為0
- foo.counter++;
- console.log(foo.counter);
- }
- for (var i = 0; i <=5; i++){
- foo();
- }
上述代碼運(yùn)行結(jié)果(在firebug控制臺中)是列出了變量c和函數(shù)funcA。注意,上述示例中var a和函數(shù)funcB的聲明仍然可能是有意義的。它們可以用作構(gòu)造函數(shù)中使用的輔助變量和輔助函數(shù)。
在C語言,以及c++在某些情況下,從函數(shù)中返回一個非基本類型的局部變量通常是不允許的。因為c/c++是按值傳遞,當(dāng)函數(shù)結(jié)束時,其堆棧被復(fù)位?;绢愋停ㄈ鏸nt,char)其值可以直接按值傳遞出去,不會產(chǎn)生任何問題,但其它變量如果是按地址傳遞的話,其地址由于在堆棧中,因此該變量的數(shù)據(jù)會隨堆棧的復(fù)位而消亡。但在Javascript,Java和C#等語言中,這樣做是允許的。理論上它們?nèi)匀皇莻髦敌驼Z言,但由于它們傳遞的是變量的引用,而變量始終產(chǎn)生在堆上(沒有明確的語言規(guī)范和教程說明Javascript的變量位置),因此函數(shù)結(jié)束后,變量要么被回收(沒有被引用的情況下),要么繼續(xù)有效。
靜態(tài)變量
一眾語言都支持靜態(tài)變量,但遺憾的是Javascript并不支持。好在仍然有方法可以模擬出靜態(tài)變量。靜態(tài)變量的實(shí)質(zhì)是它是函數(shù)作用域,但又不隨每次進(jìn)入函數(shù)體而被初始化。由于Javascript中函數(shù)本身也是一種對象,因此可以這樣:
- function foo() {
- foo.counter = foo.counter || 0; // 將計數(shù)器初始化為0
- foo.counter++;
- console.log(foo.counter);
- }
- for (var i = 0; i <=5; i++){
- foo();
- }
運(yùn)行結(jié)果為輸出1~6個數(shù)字。如果是匿名函數(shù)的話,可以用arguments.callee來代替函數(shù)名:
- function foo() {
- arguments.callee.counter = arguments.callee.counter || 0; // 將計數(shù)器初始化為0
- arguments.callee.counter++;
- console.log(arguments.callee.counter);
- }
- for (var i = 0; i <=5; i++){
- foo();
- }
this變量
在函數(shù)(不包括構(gòu)造函數(shù))中使用this變量,this的值需要等到函數(shù)調(diào)用時,由其上下文環(huán)境確定。
在構(gòu)造函數(shù)中使用this,其結(jié)果是引用到由構(gòu)造函數(shù)通過new生成的那個對象上。
在字面量對象中定義的函數(shù),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();
第一次運(yùn)行my.init()的結(jié)果顯示“not inited”,但第二次運(yùn)行的結(jié)果就是”already inited.”。同時,結(jié)果顯示this為一個Object,而非Window。因此,只要在字面量對象內(nèi)聲明的函數(shù),this都會始終綁定到當(dāng)前的字面量對象上,無論是在:{}還是在其中的函數(shù)聲明中。但要注意,this無法傳遞。即如果將this傳遞給一個函數(shù)作為參數(shù),則在函數(shù)內(nèi)部訪問到的this,并不一定是傳入的this值。
但是,值得注意的是,在字面量對象的屬性表達(dá)式中使用this,此時this并非引用到字面量對象,而是當(dāng)前定義字面量的作用域?qū)ο笊?。比如?/p>
- var my = {
- mem : "hello",
- msg : this.mem + " world!"
- };
- console.log(my.msg);
上例會顯示’undefined world’。這是因為this引用到window對象上,而當(dāng)前window對象中并無mem這一屬性。
在眾多的Javascript編程書籍中,沒有一本提到上面的例子,這不能不說是個遺憾。使用字面量對象來構(gòu)建程序中的單例對象是一種較普通的設(shè)計模式,在定義某些變量時,不可避免地要用到其它變量。比如在定義環(huán)境配置時,常常會先定義一個home,再定義一些相對于該home的path。但是這里沒有捷徑可走。下面的定義都會引起運(yùn)行時錯誤:
- var my= {
- home : "http://home",
- jsdir: my.home + "/js",
- init : function(){
- console.log("init");
- },
- start : function(){
- console.log("start");
- my.init();
- }
- };
- my.start();
錯誤的原因可能是因為,上述語句作為一個詞句執(zhí)行,因此當(dāng)為jsDir/imgDir賦值時,對象my還沒有創(chuàng)建起來,因此還不能引用my.home。而使用this之所以錯誤的原因,則已經(jīng)在前面講過了。然而,如果去掉第2行,則該代碼可以運(yùn)行,盡管我們看到第8行也引用到了my.init();這是因為,第8行只是定義,并非執(zhí)行;而第2行時需要立即對my.home進(jìn)行求值,所以會發(fā)現(xiàn)my沒有定義。這是在firebug中看到的行為,是否有某些brower并非如此,待考。