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