天天看點

詳解JavaScript作用域(全局作用域 / 局部作用域 / 作用域鍊)

什麼是作用域:作用域是可通路變量的集合。

  • 在 JavaScript 中, 對象和函數同樣也是變量。
  • 在 JavaScript 中, 作用域為可通路變量,對象,函數的集合。
  • JavaScript 函數作用域: 作用域在函數内修改。

全局作用域:

全局作用域貫穿整個javascript文檔,在所有函數聲明或者大括号之外定義的變量,都在全局作用域裡。一旦你聲明了一個全局變量,那麼你在任何地方都可以使用它,包括函數内部。事實上,JavaScript預設擁有一個全局對象window,聲明一個全局變量,就是為window對象的同名屬性指派。如下面代碼所示。 注:若變量在函數内部沒有聲明(未使用var關鍵字),該變量為全局變量。全局變量在頁面關閉後銷毀。

function zxxFn () {
        zxxs = 2
    }
    var zxx = 1
    console.log(window.zxx) // 1
    // console.log(zxxs) 未定義
    zxxFn() // 執行後zxxs成為全局變量
    console.log(zxxs) // 2
           

局部作用域(函數作用域和塊級作用域)

在JavaScript中,任何定義在函數體内的變量或者函數都将處于函數作用域中,這些變量也無法被在函數外部使用。(當然使用閉包也可以通路)

  • 變量在函數内聲明,變量為局部作用域。
  • 局部變量:隻能在函數内部通路。
  • 局部變量隻作用于函數内,是以不同的函數可以使用相同名稱的變量。
  • 局部變量在函數開始執行時建立,函數執行完後局部變量會自動銷毀。

局部作用域外部無法通路示例:

function zxxFn () {
        var zxx = 'zxx is a great girl'
        console.log(zxx) // zxx is a great girl
    }
    zxxFn() // zxx is a great girl
    console.log(zxx) // Uncaught ReferenceError: zxx is not defined 函數内部定義的局部變量外部無法通路
           

當函數體内局部變量和函數體外的變量重名的話,内部局部變量将會遮蓋同名的全局變量。(部分知識涉及變量提升)

var zxx = 'zxx is a good girl'
    function zxxFn () {
        console.log(zxx) 
        var zxx = 'zxx is a great girl'
        console.log(zxx)
        zxx = 'very good'
        console.log(zxx)
    }
    console.log(zxx)
    zxxFn()
    console.log(zxx)

    // 依次輸出 1. zxx is a good girl 2. undefined 3. zxx is a great girl 4. very good 5.zxx is a good girl
           
var zxx = 'zxx is a girl'
    var zxxs = 'yes'
    function zxxFn () {
        console.log(zxx) // undefined
        console.log(zxxs) // yes
        zxx = 'very good'
        zxxs = 'not'
        console.log(zxx) // very good
        console.log(zxxs) // not
        var zxx = 'zxx is a great girl'
        console.log(zxx) // zxx is a great girl
        console.log(zxxs) // not
    }
    console.log(zxx) // zxx is a girl
    console.log(zxxs) // yes
    zxxFn()
    console.log(zxx) // zxx is a girl
    console.log(zxxs) // not
           

作用域鍊:

周遊嵌套作用域鍊的規則很簡單:引擎從目前的執行作用域開始查找變量,如果找不到, 就向上一級繼續查找。當抵達最外層的全局作用域時,無論找到還是沒找到,查找過程都會停止。

簡單來講,局部作用域(如函數作用域)可以通路到全局作用域中的變量和方法,而全局作用域不能通路局部作用域的變量和方法。

出現函數嵌套函數,則就會出現作用域鍊。

zxxAge = 'zxx is 18 years old'
function zxxFn () {
    console.log(zxxAge)
    var zxxAge = 'zxx is 8 years old'
    var zxxSub = 'zxx is 25 years old'
    console.log(zxxAge) // zxx is 8 years old
    // console.log(zxxSub) // zxxSub is not defined
    function zxxFnSub () {
        var zxxSub = 'zxx is 18 years old'
        console.log(zxxSub) // zxx is 18 years old 
        console.log(zxxAge) // zxxSub is not defined
    }
    zxxFnSub()
}
zxxFn()
           
詳解JavaScript作用域(全局作用域 / 局部作用域 / 作用域鍊)

如圖:在執行變量列印的時候,會先在第一作用域尋找該變量是否存在,存在則使用該作用域的變量,不存在則向上尋找第二作用域,以此類推一直找到全局作用域。如果找不到則會抛出異常。

例:

function zxxFn (a) {
        console.log(a + b)
    }

    var b = 2
    zxxFn(2) // 4

對變量b查詢無法在函數zxxFn内部,但可以在上一級作用域中完成(全局作用域)
注:代碼中隐式的 a=2 操作可能很容易被你忽略掉。
           

比較下列兩個代碼

function zxxFn (a) {
    console.log(a + b) // b變量在任何相關的作用域都找不到,引擎就會抛出ReferenceError異常
    b = a
}
zxxFn(2)

=================

function zxxFn (a) {
    b = a
    console.log(a + b)
}
zxxFn(2) // 4
           

函數被調用之前作用域鍊已經存在:

zxxAge = 'zxx is 18 years old'
function zxxFn () {
    console.log(zxxAge)
    var zxxAge = 'zxx is 8 years old'
    var zxxSub = 'zxx is 25 years old'
    console.log(zxxAge) // zxx is 8 years old
    // console.log(zxxSub) // zxxSub is not defined
    function zxxFnSub () {
        console.log(zxxSub) // zxx is 25 years old
        console.log(zxxAge) // zxxSub is not defined
    }
    return zxxFnSub;
}
var zxx = zxxFn()
zxx()
           

上述代碼,在函數被調用之前作用域鍊已經存在:

  • 全局作用域 -> zxxFn函數作用域 -> zxxFnSub函數作用域

    當執行【zxx();】時,由于其代指的是zxxFnSub函數,此函數的作用域鍊在執行之前已經被定義為:全局作用域 -> zxxFn函數作用域 -> zxxFnSub函數作用域,是以,在執行【zxx();】時,會根據已經存在的作用域鍊去尋找變量。

var zxx = 'zxx is a great girl'
function zxxFn() {
    console.log(zxx)
}
function zxxFnSub() {
    var zxx = 'zxx is 18 years old'
    return zxxFn
}
var myZxx = zxxFnSub()
myZxx() // zxx is a great girl
           

上述代碼,在函數被執行之前已經建立了兩條作用域鍊:

  • 全局作用域 -> zxxFn函數作用域
  • 全局作用域 -> zxxFnSub函數作用域

當執行【myZxx();】時,myZxx代指的zxxFn函數,而zxxFn函數的作用域鍊已經存在:全局作用域 -> zxxFn函數作用域,是以,執行時會根據已經存在的作用域鍊去尋找。

最小特權原則:

也叫最小授權或最小暴露原則,這個原則是指在軟體設計中,應該最小限度地暴露必要内容,而将其他内容都“隐藏”起來,比如某個子產品或對象的 API 設計。這個原則可以延伸到如何選擇作用域來包含變量和函數。如果所有變量和函數都在全局作用域中,當然可以在所有的内部嵌套作用域中通路到它們。但這樣會破壞前面提到的最小特權原則,因為可能會暴漏過多的變量或函數,而這些變量或函數本應該是私有的,正确的代碼應該是可以阻止對這些變量或函數進行通路的。

例:

function doSomething (a) {
    b = a + doSomethingElse(a * 2)
    console.log(b * 3)
}
function doSomethingElse (a) {
    return a - 1
}
var b
doSomething(2) // 15
           

在這個代碼片段中,變量 b 和函數 doSomethingElse(..) 應該是 doSomething(..) 内部具體 實作的“私有”内容。給予外部作用域對 b 和 doSomethingElse(..) 的“通路權限”不僅 沒有必要,而且可能是“危險”的,因為它們可能被有意或無意地以非預期的方式使用, 進而導緻超出了 doSomething(..) 的适用條件。更“合理”的設計會将這些私有的具體内 容隐藏在 doSomething(..) 内部,例如:

function doSomething (a) {
    function doSomethingElse (a) {
        return a - 1
    }
    var b
    b = a + doSomethingElse(a * 2)
    console.log(b * 3)
}
doSomething(2) // 15
           

現在,b 和 doSomethingElse(..) 都無法從外部被通路,而隻能被 doSomething(..) 所控制。 功能性和最終效果都沒有受影響,但是設計上将具體内容私有化了,設計良好的軟體都會 依此進行實作。

塊級作用域

詳解JavaScript作用域(全局作用域 / 局部作用域 / 作用域鍊)

繼續閱讀