博客 / 詳情

返回

學廢了,JavaScript 中的作用域與作用域鏈

什麼是作用域?

作用域定義了變量的可見性或可訪問性。大白話來説,就是一個變量能不能被訪問或引用,是由它的作用域決定的。

在 JavaScript 中有三種作用域。

  • 全局作用域
  • 函數作用域(局部作用域)
  • 塊作用域
let globalVariable = "我是全局作用域下的變量"
function func() {
    let localVariable = "我是局部作用域下的變量"
}

if (true) {
    let blockVariable = "我是塊作用域下的變量"
}

全局作用域 Global Scope

一個在最外層定義的變量便處於全局作用域,全局作用域內的變量可以在程序的任意地方訪問。

var globalVariable = "全局作用域變量"
function func() {
    // 在函數內訪問全局作用域的變量
    console.log("函數內訪問:", globalVariable)
}

func()
console.log("函數外訪問:", globalVariable)

輸出:

函數內訪問: 全局作用域變量
函數外訪問: 全局作用域變量

使用 var 關鍵字 在大括號內(包括純粹的大括號、if、while、for)定義的變量仍然`屬於全局作用域。

if (true) {
    var globalVariable = "全局作用域變量"
}
console.log("外部訪問:", globalVariable)

輸出:

外部訪問: 全局作用域變量

函數作用域(局部作用域) Function Scope(Local Scope)

在函數內定義的變量則屬於函數作用域,又稱局部作用域。局部作用域內的變量只能在自身作用域內被訪問。

function func(params) {
    var localVariable = "局部作用域變量"
    console.log("函數內訪問:", localVariable)
}

func()
console.log("外部訪問:", localVariable) // Uncaught ReferenceError: localVariable is not defined

輸出:

函數內訪問: 局部作用域變量
Uncaught ReferenceError: localVariable is not defined

例子中,我們嘗試在外部訪問局部作用域中定義的變量,報了 變量未定義 的錯誤。

塊作用域 Block Scope

ES6 中引入了 let 與 const,與 var 不同的是。之前的例子中,在大括號(包括純粹的大括號、if、while、for)間用 var 定義的變量處在全局作用域。如果我們用 let 與 const 在大括號中定義,變量將處於塊作用域。

塊作用域內的變量只能在自身作用域內被訪問。

let 與 const 的不同點在於, const 定義的是一個常量,無法修改定義後的值。

{
    let blockVariable = "塊作用域變量"
    console.log("塊內訪問:", blockVariable)
}
console.log("外部訪問:", blockVariable) // Uncaught ReferenceError: blockVariable is not defined

輸出:

塊內訪問: 塊作用域變量
Uncaught ReferenceError: blockVariable is not defined

例子中,我們嘗試在外部訪問塊作用域中定義的變量,報了 變量未定義 的錯誤。

什麼是作用域鏈? Scope Chain

當一個變量在當前作用域無法找到時,便會嘗試尋找其外層的作用域,如果還找不到,再繼續往外尋找(只會往外尋找,不會尋找兄弟作用域,更不會往內尋找)。這種如同鏈條一樣的尋找規則便被稱為作用域鏈。

let variable1 = "我是變量 1,外部的"
let variable2 = "我是變量 2"
function func() {
    let variable1 = "我是變量 1,內部的"
    {
        let variable3 = "我是變量 3"
    }
    {
        // 往外尋找,在上一層函數內找到了
        console.log(variable1)
        // 往外尋找,直到全局作用域
        console.log(variable2)
        // 找不到,報錯
        console.log(variable3) // Uncaught ReferenceError: variable3 is not defined
    }
}
func()

輸出:

我是變量 1,內部的
我是變量 2
Uncaught ReferenceError: variable3 is not defined

在例子中,打印 variable1 變量時,由於在上層作用域也就是函數中就找到了 variable1 變量,便停止了尋找,不會找到全局作用域下的 variable1 變量。

尋找 variable2 變量時,在上層作用域中未找到,便一直找到了上上層作用域,也就是全局作用域下的 variable2 變量。

尋找 variable3 變量時,由於 variable3 變量被定義在兄弟作用域中,並不會被尋找到,因為作用域鏈的規則是隻會往上層作用域尋找,並不會尋找兄弟作用域。因此這裏報了變量未定義的錯誤。

函數的作用域是它定義時的作用域,而不是調用時

function func() {
    let variable = "我是 func 內的變量"
    function func2() {
        console.log(variable)
    }
    return func2
}

{
    let variable = "我是大括號內的變量"
    let func2 = func()
    func2()
}

輸出:

我是 func 內的變量

在例子中,執行 func2 函數時往上尋找的作用域是在 func2 定義時的作用域,而不是調用時的作用域。

如果找不到變量會怎樣?

如果一個變量直到全局作用域也找不到便會執行以下操作。

  1. 非嚴格模式:隱式聲明全局變量
  2. 嚴格模式:報錯

非嚴格模式

非嚴格模式下,嘗試賦值一個變量時,如果找不到則會隱性聲明成全局域的變量。

{
    variable = "我是一個隱性聲明的變量"
}
console.log(variable)

輸出:

我是一個隱性聲明的變量

以上的例子中,variable 由於未聲明,因此被隱性聲明成了全局作用域下的變量,這使得在最外部也能打印出 variable 變量的值。

非嚴格模式下,嘗試使用一個變量的值時,如果找不到同樣會報錯。

{
    console.log(variable) // Uncaught ReferenceError: variable is not defined
}

輸出:

Uncaught ReferenceError: variable is not defined

以上的例子中,由於使用 variable 時未定義,因此報了未定義的錯誤。

嚴格模式

加入 "use strict" 表明是嚴格模式,嚴格模式下不論賦值還是使用未事先聲明的變量都會報錯。

"use strict"
{
    variable = "我是一個隱性聲明的變量" // Uncaught ReferenceError: variable is not defined
}

輸出:

Uncaught ReferenceError: variable is not defined

作用域的好處?

  1. 防止命名衝突:你寫了一萬行的代碼文件,如果沒有作用域,你要給每個變量取獨一無二的名字,屁股想想也知道是種折磨。
  2. 安全性: 變量不會被外部訪問,保證了變量值不會被隨意修改。你定義在函數內的變量,如果能在幾千行之後不小心被修改,腳趾頭想想也知道是種折磨。
  3. 更高級的語法:封裝、面向對象等的實現離不開對變量的隔離,這是依靠作用域所達到的。

説人話!

寫代碼時不用區分它什麼全局使用域、局部作用域、塊作用域啥的概念。只用記得大括號就是一個作用域,尋找變量永遠是從內往外找。現在我們的編輯器基本都有縮進格式化, 從當前代碼塊的位置一層一層往左,就是它所能引用到的所有變量。

打個比方,就像我們每個家庭就是一個作用域,當我們需要一筆手術費掏不出錢的時候,肯定是先在家裏找,問問父母兄弟姐妹啥的,不會去求助其他陌生的家庭。還沒有的話就往外到熟人關係這個作用域裏問問。還不行就向街道居委會求助。居委會也沒辦法再向國家求援。從最親近的關係找起,一層一層圈子往外,這就是作用域與作用域鏈。

最後強烈建議大家使用 let 命名變量,放棄 var!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.