對JavaScript的變量作用域的理解
作用域是一個抽象概念,在瀏覽器的JavaScript實現(xiàn)中會有這個概念,但是不同瀏覽器實現(xiàn)可能不同。
一、作用域的種類(自己感悟)
1.對象作用域,window及標簽元素都屬于對象作用域。
2.函數(shù)作用域,函數(shù)自身形成的作用域。
可以把作用域看作是上下文(context)之類的對象。
看下面的代碼
例0
1
breakE = 0;
2
var state = 1; //1
3
Person = function(name) { //2
4
var age = 20;
5
this.name = name;
6
this.show = function () { //3
7
alert(age);
8
alert(state);
9
alert(name);
alert(this.name);
10
}
11
alert(m);
12
}
13 Person.prototype.check = function () {};
14
m = new Person('mike'); //4
15
var j = new Person('Jerry'); //5
16
17
m.show(); //6
通過看上面這段代碼我們發(fā)現(xiàn)有時候定義變量時用var,有時候卻不用,這里有什么區(qū)別嗎?在此之前我們先定下規(guī)矩,那就是必須有一個全局作用域,就像是window對象的存在一樣,是作用域的最頂層。那么哪些變量被定義到作用域的最頂層呢?那就是state,對于不在任何函數(shù)或類中用var定義的變量存在于全局作用域中。那么全局作用域?qū)儆谧饔糜虻哪姆N類型呢?那就是對象作用域。具體是哪個對象的作用域呢?非window對象莫屬。
2

3

4

5

6

7

8

9

alert(this.name);
10

11

12

13 Person.prototype.check = function () {};
14

15

16

17

我們可以把對象作用域想像成兩部分,左面部分是對象級別的,保存的是和對象綁定的成員屬性(無論是變量還是函數(shù)),通過this來訪問的。右面的部分是作用域級別的,保存的是var定義的屬性(無論是變量還是函數(shù)),可以直接通過變量名稱訪問。對于window來說它是個特殊體,從用var定義的變量可以用this來訪問就能看出問題,我個人猜測window對象既是JavaScript的頂層對象又是頂層作用域,所以才會有這種效果。
另一種情況就是標簽元素的對象作用域和全局作用域非常的像,如下例:
1
//例1
2
<input type='button' value='click me' onclick='alert(value)'/>
3
//例2
4
<input type='button' value='click me' onclick='alert(this.value)'/>
5
//例3
6
function test() {
7
alert(value);
8
alert(this.value);
9
}
10
<input type='button' value='click me' onclick='test()'/>
很奇怪為什么例1和例2訪問value和全局作用域里的var定義的變量的行為是一致的,按理說value應(yīng)該屬于input標簽元素對象的成員屬性,訪問對象的成員屬性應(yīng)該用this來訪問,我們自定義的類,如果成員方法想訪問成員變量都需要加this的,就像最上面的例子里的this.show方法訪問this.name成員變量一樣。而對于例3,勤快的讀者如果運行一下便知一定會出錯的,第一句系統(tǒng)會報value未定義(除非你在全局作用域定義過value變量),第二句會打印出undefined。為什么會這樣呢?這就要引出作用域鏈這個概念了,后面會解釋這個概念的。
2

3

4

5

6

7

8

9

10

反觀函數(shù)作用域,由于函數(shù)中用var定義的變量不能用this來訪問,所以在函數(shù)作用域中不再像對象作用域那樣分兩部分,而this實際保存的是與該函數(shù)綁定的對象的引用,也就是說this在函數(shù)中有特殊的含義,是不能挪作它用的。
二、作用域鏈
作用域是可以嵌套的,而嵌套的結(jié)果就形成了作用域鏈,如例0所示,我們來描述一下作用域鏈:
全局作用域<----Person函數(shù)作用域<-----show函數(shù)作用域
在全局作用域中可以訪問哪些變量呢(注意我現(xiàn)在指的只是作用域中的變量)?
1.state
2.j
在Person函數(shù)作用域中可以訪問哪些變量呢?
1.全局作用域中的所有可訪問的變量
2.name(參數(shù))
3.age
在show函數(shù)中又有哪些變量可以被訪問呢?
1.全局作用域中的所有可訪問變量
2.Person函數(shù)作用域中所有可訪問變量
3.如若show函數(shù)中還定義了var變量也要加入show函數(shù)作用域
如果在show中訪問一個x變量,首先從show函數(shù)作用域里查是否有x這個變量,如果沒有再查Person作用域,如果還沒有的話再查全局作用域,找到就返回該變量。
講解到這里我們發(fā)現(xiàn)一個問題,似乎作用域是定義時生成塊級作用域(即單獨的對象作用域和函數(shù)作用域)而運行時生成作用域鏈,這就是詞法作用域的由來。有了這種作用域就構(gòu)成了閉包的基礎(chǔ)。
下面是一個稍微特殊的例子:
1 function test() {
2 test2();
3 }
4
5 function test3() {
6 test2 = function() {
7 alert("test2");
8 }
9 test();
10 }
定義test的時候test2還不存在,但是調(diào)用test的時候,test2就存在了,這說明了作用域鏈確實是運行時生成的。2 test2();
3 }
4
5 function test3() {
6 test2 = function() {
7 alert("test2");
8 }
9 test();
10 }
通過作用域鏈如何解釋例1、2、3呢?
我的分析是對于HTML元素來說當頁面解析的時候,元素被解析成DOM樹,HTML元素里的標準屬性作為該元素對象作用域的變量(類似var定義的那種),而非該元素DOM對象的成員屬性(類似this定義的那種),這樣就解釋了兩個問題:
1.在頁面上預(yù)定義的HTML元素標準屬性是不能用delete和removeAttribute刪除的。
2.onclick屬于input元素的一個方法,那么這個作用域鏈應(yīng)該像下面這樣:
全局作用域<-----HTML元素作用域<-------onclick函數(shù)作用域
value變量在哪個作用域里呢?答案是HTML元素作用域中。所以onclick不但可以訪問而且加不加this都可以。
那么例3的作用域鏈是什么樣的呢?如下:
全局作用域<-----test函數(shù)作用域
全局作用域<-----HTML元素作用域<-------onclick函數(shù)作用域
沒錯是兩條,因為當單擊按鈕后執(zhí)行到onclick函數(shù)中時走的是第二條鏈,而在onclick中調(diào)用了test函數(shù)時,走的是第一條鏈。
顯然value還是定義在HTML元素作用域中,而test想訪問value值一定是訪問不到的。
對于這個問題我還測試了一下非標準屬性,如下例:
1
<input type="button" value="click me" bb="123" onclick="delete this.bb; alert(bb)"/>
測試發(fā)現(xiàn)非標準的HTML元素屬性可以被刪除,但是delete語句執(zhí)行時必須用this來引用(IE中測試),直接訪問如alert到?jīng)]有這個限制。
說明bb是input對象屬性是沒錯的,因為它可以被刪除,但是如果它沒有被刪除的話alert(bb)還是能顯示出來的,說明這類屬性很特殊,具體怎么解釋我還沒有想好,如果有朋友能解釋的通,請告知我。
三、什么是閉包
show函數(shù)顯然是一個閉包,但是它是由類的構(gòu)造函數(shù)生成的,和另一種形式其實是一樣的,代碼如下:
1
function getFunc (a, b) {
2
return function () {
3
alert(a - b);
4
}
5
}
6
7
var func1 = getFunc(10,5);
8
func1();
9
10
var func2 = getFunc(20,30);
11
func2();
上面的代碼func1函數(shù)無論我執(zhí)行多少次都顯示5,func2函數(shù)無論我執(zhí)行多少次都顯示-10,這和例0是多么的像呀,例0中m.show()無論執(zhí)行多少次都能顯示'mike',j.show()無論執(zhí)行多少次都能顯示'Jerry'(行號9那行在起作用,先不考慮this.name那行)。這就是閉包,把作用域鏈中變化的變量給固定化,然后把作用域鏈和函數(shù)本身進行綁定,無論我在什么地方執(zhí)行函數(shù),函數(shù)的作用域鏈始終是它定義的時候那條。
2

3

4

5

6

7

8

9

10

11

四、關(guān)于對象成員的訪問(即this能引用的那些屬性),我只是在猜想,可能的作用域鏈應(yīng)該如下:
全局作用域<-------m對象作用域<-------m.test函數(shù)作用域
m的對象成員(類中this定義的)保存在對象作用域的左半部,m的作用域變量(類中var定義的)保存在對象作用域的右半部。
m.test函數(shù)要訪問對象成員時從對象作用域的左半部檢索,要訪問作用域變量時從對象作用域的右半部檢索。
同時這個作用域鏈還解答了另一個問題,即原型方法(公共方法)為什么不能訪問類中用var定義的變量。
看例0即可知check和Person根本就不在同一個鏈上,又談何能訪問呢?
至此把JavaScript變量作用域講解完畢,我覺得這個還是蠻重要的,如果一定要有個比較的話,應(yīng)該和Java中的ClassLoader有同樣的重要性吧。大家借鑒的同時請多給我指出錯誤,因為這其中有一些還是我的猜測。
posted on 2009-05-19 02:06 Eric Song 閱讀(509) 評論(0) 編輯 收藏 所屬分類: JavaScript