JavaScript有兩種變量:局部變量和全局變量。而這兩個對於初學JavaScript的同學來說是一個較爲麻煩的。非常容易搞錯。我也看了好幾天有關於這方面的教程,也還沒完全整明白。今天把自己理解的記錄下來,有不對之處,還請高手斧正。
在深入瞭解JavaScript中的局部變量和全局變量,我們必須要了解以下幾個方面:
- 如何聲明局部變量和全局變量
- JavaScript的變量作用域是基於特有的作用域鏈的
- JavaScript中沒有塊級作用域
- 函數中聲明的變量在整個函數中都有定義
如何聲明局部變量和全局變量
在JavaScript中,常常把定義在函數外的變量稱爲全局變量;而在函數內聲明的變量稱爲局部變量。
來看一個例子:
var variable = 'global';
function checkVariable () {
var variable = 'local';
console.log(variable); // local
}
checkVariable();
console.log(variable); // global
上面的示例,在函數checkVariable()
外聲明瞭一個全局變量variable
,同時在函數內聲明瞭一個局部變量variable
。打個比方來說,把函數checkVariable()
當作一個容器,那麼放在容器裏的變量就是局部變量,而放在容器外的變量則是全局變量。當然,這也不是一塵不變的。爲什麼這麼說呢?我們還是繼續用代碼來說話吧,因爲代碼比我更能闡述要說的一切:
variable = 'global';
function checkVariable() {
variable = 'local';
console.log(variable); // local
myVariable = 'local';
console.log(myVariable); // local
}
checkVariable();
console.log(variable); // local
console.log(myVariable); // local
對於我這樣的初學者來說,我很多時候搞不清楚,爲什麼在函數外,variable
的值也變成了local
?爲什麼在函數體外可以訪問myVariable
變量?
其實這兩個問題都源於一個特性。在全局作用域中聲明變量可以省略var
關鍵字,但如果在函數體內聲明變量時不使用var
關鍵字,就會發生上面這樣的現象。
另外,第一個示例告訴我們,在函數體內部,局部變量的優先級比同名的全局變量要高。那麼在聲明局部變量的函數體範圍內,局部變量將覆蓋同名的全局變量。
感覺有點囉嗦,不過還是做個小小總結:
聲明局部變量一定要使用var
關鍵字,使用var
關鍵字聲明變量時,變量會自動添加到距離最近的可用環境中。如果沒有寫var
,
變量就會暴露在全局上下文中, 這樣很可能會和現有變量衝突. 另外, 如果沒有加上, 很難明確該變量的作用域是什麼, 變量也很可能像在局部作用域中, 很輕易地泄漏到 Document
或者 Window
中, 所以務必用var
去聲明變量。如果變量在未聲明的情況下被初始化,該變量會自動添加到全局環境。
變量作用域鏈
如果要理解JavaScript中變量是局部變量還是全局變量,那就必須要先理解變量作用域鏈。要理解這個就得先整明白執行環境。
每當JavaScript執行時,都會有一個對應的執行環境被創建,執行環境中很重要的一部分就是函數的調用對象,而調用對象就是用來存儲相應函數的局部變量的對象。每一個JavaScript方法都是在自己獨有的執行環境中運行。
簡而言之,函數的執行環境包含了調用對象,調用對象的屬性就是函數的局部變量,每個函數就是在這樣的執行環境中執行,而在函數之外的代碼,也在一個環境中執行,這個執行環境包含了全局變量。
在JavaScript的執行環境中,還有一個詞要理解,那就是與執行環境對應的作用域鏈,它是一個由對象組成的列表或鏈。
在理解作用域鏈之前先來看一個示例:
var rain = 1;
function rainMan () {
var man = 2;
function inner () {
var innerVar = 4;
console.log(rain); // 1
}
inner(); // 調用inner函數
}
rainMan(); // 調用rainMan函數
執行上面的代碼,瀏覽器控制器會輸出rain
的值,其值爲1
。先來看一張執行的示意圖:
執行rainMan()
函數後,會執行其內部的inner()
函數,而這個函數執行console.log(rain)
會輸出一個值。這個過程在JavaScript是這樣執行的,首先在inner()
函數中查找是否聲明瞭變量rain
,如果聲明瞭則會使用inner()
函數中的rain
變量;如果inner()
函數中未聲明變量rain
,JavaScript則會繼續在rainMan()
函數中查找是否定義了rain
變量。和inner()
函數查找方式一樣,如果在rainMan()
函數中聲明瞭變量rain
,則會使用這個聲明的變量;如果函數內沒有定義rain
變量,則JavaScript則會繼續往向(全局對象)查找是否定義了rain
變量。在這個示例中,inner()
和rainMan()
函數中並未聲明rain
變量,而在全局對象中聲明瞭rain=1
,因此函數執行完最終結果會輸出1
。如果在全局對象中都未聲明rain
變量的話,則會輸出報錯信息:Uncaught
ReferenceError: raine is not defined
。
作用域鏈:JavaScript需要查詢一個變量x
時,首先會查找作用域鏈的第一個對象,如果在第一個對象中沒有定義x
變量,JavaScript會繼續查找有沒有定義x
變量,如果第二個對象沒有定義則會繼續查找,以此類推。上面的代碼涉及到了三個作用域鏈對象,依次是:inner
、rainMan
、window
。
在全局的 Javascript 執行環境中,作用域鏈中只包含一個對象,就是全局對象。而在函數的執行環境中,則同時包含函數的調用對象。由於 Javascript 的函數是可以嵌套的,因此每個函數執行環境的作用域鏈可能包含不同數目個對象,一個非嵌套的函數的執行環境中,作用域鏈包含了這個函數的調用對象和全局對象,而在嵌套的函數的執行環境中,作用域鏈包含了嵌套的每一層函數的調用對象以及全局變量。
上圖來自於@範斌寫的《理解 Javascript 中變量的作用域》文章中。
JavaScript中沒有塊級作用域
在一些程序語言中,比如C或者Java等語言,if
、for
這樣的語句塊中可以包含自己的局部變量,這些變量的作用域是這些語句塊的,而在JavaScript中卻不存在塊級作用域這樣的說法。
仔細看看下面的示例:
function checkVariable (obj) {
var i = 0;
if (typeof obj == 'object') {
var j = 0;
for (var k = 0; k < 3; k++) {
console.log(k); // 0,1,2
}
console.log(k); // 3
}
console.log(j); // 0
}
checkVariable(new Object());
你會發現變量i
,j
,k
作用域是相同的,他們都在checkVariable()
函數內都是全局的。也就是說,在函數中聲明的所有變量,無論是在哪裏聲明的,在整個函數中它們都是有定義的。
函數中聲明的變量在整個函數中都有定義
在JavaScript中,函數中聲明的所有變量,無論是在哪裏聲明的,在整個函數中它們都是有定義的,即使在聲明之前。其實這裏隱藏一個坑,來看看下面的示例代碼:
function checkVariable() {
var a = 1;
function inner () {
a = 100;
}
inner();
console.log(a); // 100
}
checkVariable();
上面的代碼說明了,變量a
在整個checkVariable()
函數體內都可以使用,並可以重新賦值。由於這條規則,給入門者留埋下一個坑:
var a = 1;
function checkVariable () {
console.log(a); // undefined
var a = 2;
console.log(a); // 2
}
checkVariable();
在上面的示例中,console.log(a)
輸出的是undefined
,既不是全局變量a
的值1
,也不是全局變量a
的值10
。是由於在函數checkVariable()
內局部變量a
在整個函數內都有定義(var
a = 2
進行了聲明),所以在整個checkVariable()
函數內隱藏了同名的全局變量a
。所以纔會輸出undefined
。
上面的代碼其實就等同於:
var a = 1;
function checkVariable() {
var a;
console.log(a);
a = 2;
console.log(a)
}
checkVariable();
簡單總結一下
JavaScript的變量的域是根據方法塊來劃分的(也就是說以function
的一對大括號{}
來劃分)。切記:是function
塊,而不是for
、while
和if
塊。因爲在JavaScript中沒有塊作用域。
function checkVariable() {
console.log(i); //i=> undefined i未賦值
for (var i = 0; i < 3; i++) {
console.log(i); // i=>0,1,2;當i爲3時跳出循環
}
console.log(i); // i=>3 i在for循環外,但i的值仍然保留爲3
while (true) {
var j = 1;
break;
}
console.log(j); // j=>1; j在while循環外,但j的值仍然保留爲1
if (true) {
var k = 1;
}
console.log(k); // k=>1; k已經在if循環外,但k的值仍然保留爲1
}
checkVariable();
console.log(i); // Uncaught ReferenceError: i is not defined; 程序報錯,中斷腳本執行
console.log(j); // 未執行
console.log(k); // 未執行
JavaScript在執行前會對整個腳本做完整的分析(包括局部變量),從而確定變量的是局部變量還是全局變量。如下面的示例代碼:
var a = 1;
function checkVariable () {
console.log(a); // a=> undefined
a = 2;
console.log(a); // a=> 2
var a;
console.log(a); // a=> 2
}
checkVariable();
console.log(a); // a=>1
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
原文: https://www.w3cplus.com/javascript/the-basics-of-variable-scope-in-javascript.html © w3cplus.com