js中,函式的閉包、作用域跟[[Scopes]]的關係

被雨水過濾的空氣不想說話發表於2018-12-06

[[Scopes]]是函式的內部屬性,是無法訪問的,但是我們可以通過Chrome的開發者工具看到它的樣子。

d

  1. 我們現在宣告個函式foo
  2. 需要檢視foo的原型物件才能看到[[scopes]]屬性,因為foo.prototype.constructorfoo指向同一個函式,所以點開constructor選項。
  3. 現在我們終於看到了[[Scopes]]屬性。

js中,函式的閉包、作用域跟[[Scopes]]的關係
可以清楚的看到它是一個陣列,只有一個元素Global也就是全域性物件,經過我目測這個Global應該和window物件是同一個物件。

// 定義一個全域性的變數
var a = 'global property'

function foo () {
    console.log(a)
}

foo() // 此時會輸出global property
複製程式碼

如上程式碼所示,此時我們再看foo[[Scopes]]屬性

js中,函式的閉包、作用域跟[[Scopes]]的關係
會發現Global物件裡增加了一個a屬性,這和window物件表現一致。事實上在函式foo裡面執行console.log(a)的時候,變數a就是從這個Global物件內讀取的。

function outer () {
    var a = 'property of outer scope'
    
    return function inner () {
        console.log(a)
    }
}

var inner = outer() 
inner() // 如大家所料,會輸出property of outer scope
複製程式碼

此時我們再看一下inner[[Scopes]]屬性

js中,函式的閉包、作用域跟[[Scopes]]的關係
會發現[[Scopes]]陣列的前面新新增了一個叫Closure的物件,從字面上看這不就是閉包的意思嗎,我們展開看一下

js中,函式的閉包、作用域跟[[Scopes]]的關係
果然有我們閉包裡的屬性a,其實inner函式裡的console.log(a)中的a就是從設個Closure物件裡面讀取的。我們再來看一個例子

function outer () {
    var a = 1
    
    function inner () {
        var b = 2
        
        return function innermost () {
            console.log(a, b)
        }
    }
    
    return inner()
}

var innermost = outer()
innermost() // 此時輸出的值是1 2
複製程式碼

看一下innermost[[Scopes]]屬性,如下圖

js中,函式的閉包、作用域跟[[Scopes]]的關係
會發現在Scopes陣列的開頭又增加了一個閉包,是按照從內到外的順序排列的,讓我們點開看一下

js中,函式的閉包、作用域跟[[Scopes]]的關係
果然,這裡的兩個閉包物件分別包含了innermost需要訪問的所有變數。這就是作用域鏈的概念,當一個函式被呼叫的時候,函式內部訪問的物件會先從函式自己的作用域內部進行查詢,如果沒找到對應的變數,就會從[[Scopes]]陣列的第一項閉包物件進行查詢,如果還沒找到就繼續到下一個閉包物件查詢,以此類推。

完。

相關文章