JavaScript 變量及其作用域

1 變量聲明

1.1 聲明方式

JavaScript 有三種聲明方式:var - 聲明一個變量,可賦一個初始化值、let - 聲明一個塊作用域的局部變量,可賦一個初始化值、const - 聲明一個塊作用域的只讀的命名常量。

1.2 重複聲明和遺漏聲明

使用 var 命令重複聲明變量是合法的,就跟重新賦值一樣。

如果你試圖讀取一個沒有聲明的變量,JavaScript 會報錯。然而,你可以給一個沒有聲明的變量賦值,此時 JavaScript 會給全局對象創建一個同名屬性,它看起來就像一個正確聲明的全局變量。實際上,當用 var 命令聲明一個全局變量時,就是定義了 JavaScript 全局對象的一個屬性。差別在於,用 var 命令聲明變量時,創建的這個屬性是不可配置的。

var x = 1   // 用 var 命令聲明並賦值
y = 2       // 遺漏聲明直接賦值
this.z = 3  // 同上
delete x    // => false
delete y    // => true
delete z    // => true

2 變量提升

var 命令會發生“變量提升”現象,即變量可以在聲明之前使用,值爲 undefined。

var x = 1           // 聲明 + 賦值 x
console.log(x, y)   // => 1 undefined
var y = 2           // 聲明 + 賦值 y

// 下面的代碼和上面的代碼是一樣的
var x = 1           // 聲明 + 賦值 x
var y               // 聲明 y
console.log(x, y)   // 此時 y 已聲明但還未賦值
y = 2               // 賦值 y 

let 和 const 命令不存在變量提升,因此會出現暫時性死區(temporal dead zone,簡稱TDZ)。暫時性死區是指一個塊級作用域內,使用 let 或者 const 命令聲明變量之前的區域。在暫時性死區內,該變量是不可用的。

{
    console.log(x)  // => ReferenceError
    let x = 1
}

3 變量作用域

3.1 全局作用域和函數作用域

全局變量擁有全局作用域,在 JavaScript 代碼中的任何地方都是有定義的。在函數體內聲明的變量只在函數體內有定義,他們是局部變量。函數參數也是局部變量,只在函數體內有定義。在函數體內,局部變量的優先級高於同名的全局變量。

var scope = 'global'
function f () { 
    console.log(scope)  // => undefined
    var scope = 'local' // 聲明 + 賦值
    console.log(scope)  // => local
}

// 下面的代碼和上面函數的代碼是一樣的
function f () {
    var scope           // 聲明局部變量,覆蓋同名的全局變量
    console.log(scope)  // 此時 scope 已聲明但還未賦值
    scope = 'local'
    console.log(scope)
}

另外,因爲函數定義是可以嵌套的,每個函數都有它自己的作用域,所以會出現幾個函數作用域嵌套的情況。

3.2 塊級作用域

ES6 新增了塊級作用域。使用 let 或者 const 命令聲明的變量只在其塊級作用域內有定義。塊級作用域的出現使一些場景變得更加合理。比如:用 var 命令聲明的函數作用域內的局部變量會覆蓋同名的全局變量,而用 let 命令聲明則不會。

var scope = 'global scope'
function f () {
    console.log(scope)  // => undefined
    if (false) {
        var scope = 'hello world'
    }
}

// 用 let 命令代替 var 命令會得到不同的結果
function f () {
    console.log(scope)  // => global scope
    if (false) {
        let scope = 'hello world'
    }
}

再比如:用 var 命令聲明的用來計數的循環變量會泄露爲全局變量,而用 let 命令聲明則不會。

for (var i = 0; i < 5; i++) {
    // ...
}
console.log(i)  // => 5

// 用 let 命令代替 var 命令會得到不同的結果
for (let i = 0; i < 5; i++) {
    // ...
}
console.log(i)  // => ReferenceError

另外,for 循環還有一個特別之處,就是設置循環變量的那部分是一個父作用域,而循環體內部是一個單獨的子作用域。

for (let i = 0; i < 5; i++) {
    let i = 'hello world'
    console.log(i)  // => hello world
}

塊級作用域也是可以嵌套的。外層作用域無法讀取內層作用域的變量,內層作用域可以讀取外層作用域的變量且內層作用域的變量的優先級高於同名的外層作用域的變量。由於塊級作用域的出現,立即執行函數(IIFE)就不再必要了。

// IIFE 寫法
(function () {
    var scope = ...
    ...
})()

// 塊級作用域寫法
{
    let scope = ...
    ...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章