一個變量的作用域(scope)是程序源代碼中定義這個變量的區域。簡單的說,作用域就是變量與函數的可訪問范圍。全局變量擁有全局作用域,在JavaScript代碼中的任何地方都有定義。局部變量是在函數體內聲明而且只作用在函數體內部以及該函數體的子函數的變量。下面我們對全局作用域和局部作用域來做一個深入的理解。
1. 全局作用域(Global Scope)
全部變量擁有全局作用域,在代碼的任何地方都有定義,一般來說以下幾種情形擁有全局作用域:
(1)最外層函數和在最外層函數外面定義的變量擁有全局作用域,例如:
1 var scope="global"; //聲明一個全局變量 2 function checksope(){ 3 function showglobal(){ 4 alert(scope); //彈窗全局變量 5 } 6 showglobal(); 7 } 8 checksope() // global 內部函數可以訪問全局變量
(2)所有末定義直接賦值的變量自動聲明為擁有全局作用域,例如:
1 function checksope(){ 2 var scope="local"; 3 scopeglobal="global"; 4 alert(scope); 5 } 6 checksope(); // local 7 alert(scopeglobal); // global 不帶var關鍵詞聲明的變量, 8 直接升級為全局變量,同時也是全局變量 9 的一個屬性 10 alert(scope); //腳本錯誤
變量scopeglobal擁有全局作用域,而scope在函數外部無法訪問到。
(3)所有window對象的屬性擁有全局作用域
一般情況下,window對象的內置屬性都擁有全局作用域,例如window.name、window.location、window.top等等。
2. 局部作用域(Local Scope)
局部作用域一般只在固定的代碼片段內可訪問到,最常見的例如函數內部,所有在一些地方也會看到有人把這種作用域稱為函數作用域,例如下列代碼中的blogName和函數innerSay都只擁有局部作用域。
1 function checksocpe(){ 2 var socpe="local"; 3 function inner(){ 4 alert(socpe); 5 } 6 inner(); 7 } 8 alert(socpe); //腳本錯誤 9 inner(); //腳本錯誤 函數外部無法訪問內部定義的函數
函數作用域和提前聲明
一、函數作用域
在一些類似c語言的編程語言中,花括號內的每一段代碼都具有各自的作用域,而且變量在聲明它們的代碼段之外是不可見的,我們稱之為塊級作用域,而JavaScript沒有塊級作用域。JavaScript取而代之地使用了函數作用域:變量在聲明他們的函數體以及這個函數體嵌套的任意函數體內都有定義的。
在如下所示的代碼中,在不同位置定義了變量i、j、k,它們都在同一個作用域內——這三個變量在函數體內均是有定義的。
1 function test(0){ 2 var i = 0; // i在行函數體內時有定義的, 3 if(typof 0 == "object"){ 4 var j = 0; //j在函數體內是有定義的,不僅僅是在循環內 5 for(var k=0; k<10;k++){ //k在行函數體內是有定義的,不僅僅是在循環內 6 console.log(k);//輸出數字0-9 7 8 } 9 console.log(k); //k 已經定義了,輸出10 10 } 11 console.log(j); //j 已經定義了,但是可能沒有初始化 12 }
二、函數的提前聲明
javascript的函數作用域是指在函數內聲明的所有變量在函數體內始終是可見的,有意思的是,這意味着變量在聲明之前甚至已經可用。JavaScript的這個特性被非正式地稱為聲明提前,即JavaScript函數里聲明的所有變量(但不涉及賦值)都被"提前"至函數體的頂部。
tip:"聲明提前"這步操作是在JavaScript引擎的"預編譯"時進行的,是在代碼開始運行之前。
1 var scope = "glocal"; 2 function f(){ 3 console.log(scope);//輸出"undefined",而不是"global" 4 var scope = "local";//變量在這里賦初始值,但變量本身在函數體內任何地方均是有定義的 5 console.log(scope);//輸出"local" 6 }
也許您會誤認為第一行會輸出“global”,因為代碼還沒有執行到var語句聲明局部變量的地方。其實不然,由於函數作用域的特性,局部變量在整個函數體始終是有定義的,也就是說,在函數體內局部變量覆蓋了同名全局變量。盡管如此,只有在程序執行到var語句的時候,局部變量才會被真正賦值。因此,上述過程等價於:將函數內的變量聲明“提前”至函數頂部,同時變量初始化留在原來的位置:
function f(){ var scope; //在函數定部聲明了局部變量 console.log(scope); //undefined scope = "local";// console.log(scope); // 輸出local }
作為屬性的變量
當聲明一個全局變量時,實際上是定義了全局對象的一個屬性。使用var聲明的變量不可配置,未聲明的可配置。如下:
var truvar = 1; //聲明一個不可刪除的全局變量 fakevar = 2; //創建全局對象的一個可刪除的屬性 this.fakecar2 = 3;//同上 delete truevar //=> false:變量並沒有被刪除 delete fakevar //=> true:變量並沒有被刪除 delete this.fakevar2 //=> true:變量並沒有被刪除
此規則只對全局變量有效。
作用域鏈
1、作用域鏈變量的尋址
如果講一個局部變量看做是自定義實現的對象的屬性的話,那么可以換一個角度來理解作用域。
每一段javascript代碼(全局代碼或函數)都有一個與之關聯的作用域鏈。這個作用域連是一個對象列表或者鏈表,這組對象定義了這段代碼“作用域中”的變量。當javascript需要查找變量x的值的時候(這個過程稱作“變量解析”),它會從鏈中的第一個對象開始查找,如果這個對象有一個名為x屬性,則會直接使用這個屬性的值,如果第一個對象中不存在,則會繼續尋找下一個對象,依次類推。如果作用域鏈上沒有任何一個對象含有屬性x,則拋出錯誤(ReferenceError)異常。
2、不同的層級作用域上對象的分布
- 在javascript的最頂層(也就是不包含任何函數定義內的代碼),作用域鏈由一個全局對象組成。
- 在不包含嵌套的函數體內,作用域鏈上有兩個對象,第一個是定義函數參數和局部變量的對象,第二個是全局對象。
- 在一個嵌套的函數體內,作用域鏈上至少有三個對象。當調用這個函數時,它創建一個新的對象來存儲它的局部變量,它實際上保存在同一個作用域鏈。
對於嵌套函數來講,事情更有趣,每次調用外部函數時,內部函數又會重新定義一遍。因為每次調用外部函數的時候,作用域鏈都是不同的。內部函數每次定義的時候都有微妙的差別——在每次調用外部函數時,內部函數的代碼都是不相同的,而且關聯這段代碼的作用域鏈也不相同。