JavaScript學習筆記: 局部變量和全局變量

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變量,如果第二個對象沒有定義則會繼續查找,以此類推。上面的代碼涉及到了三個作用域鏈對象,依次是:innerrainManwindow

在全局的 Javascript 執行環境中,作用域鏈中只包含一個對象,就是全局對象。而在函數的執行環境中,則同時包含函數的調用對象。由於 Javascript 的函數是可以嵌套的,因此每個函數執行環境的作用域鏈可能包含不同數目個對象,一個非嵌套的函數的執行環境中,作用域鏈包含了這個函數的調用對象和全局對象,而在嵌套的函數的執行環境中,作用域鏈包含了嵌套的每一層函數的調用對象以及全局變量。

全局變量和局部變量

上圖來自於@範斌寫的《理解 Javascript 中變量的作用域》文章中。

JavaScript中沒有塊級作用域

在一些程序語言中,比如C或者Java等語言,iffor這樣的語句塊中可以包含自己的局部變量,這些變量的作用域是這些語句塊的,而在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塊,而不是forwhileif。因爲在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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章