從《JS 總結之函數、做用域鏈》一文中知道做用域鏈的做用,保證了對全部變量對象的有序訪問。html
函數外的是沒法訪問函數內部的變量,有時候要用到怎麼辦?咱們的主角,閉包就是能夠解決這個問題。git
引用 MDN 上的解釋:github
閉包是函數和聲明該函數的詞法環境的組合。閉包
引用 《JavaScript 高級程序設計(第 3 版)》上的解釋:函數
閉包是指有權訪問另外一個函數做用域中的變量的函數。ui
這兩個解釋都在說着同一件事,閉包能訪問聲明時函數所在的環境中的變量和函數。this
那具體是由於什麼纔會讓閉包訪問到環境中的變量和函數,這得靠兩兄弟:變量對象和做用域鏈。spa
變量對象翻譯
當執行函數的時候,會建立和初始化一個函數執行環境,在函數執行環境中,全局執行環境的變量對象(Variable Object,縮寫爲 VO)不能直接訪問,此時由激活對象(Activation Object,縮寫爲 AO)扮演 VO 的角色。設計
變量對象專門用來梳理和記住這些變量和函數,是做用域鏈造成的前置條件 。但咱們沒法直接使用這個變量對象,該對象主要是給 JS 引擎使用的。具體能夠查看《JS 總結之變量對象》。
變量對象就至關於一個存放的倉庫,獲取到裏面的東西,還得須要去獲取這些的路徑,這就是做用域鏈的事情了。
做用域鏈
然而,光有變量對象可完成不了閉包的造成,怎樣才能讓函數訪問到,這得靠做用域鏈,做用域鏈的做用就是讓函數找到變量對象裏面的變量和函數。具體能夠查看《JS 總結之函數、做用域鏈》
雖然都是講閉包,但 MDN 上面講的是聲明該函數的詞法環境,而 JS 高程講的是訪問另外一個函數做用域中,從解釋上的不一樣,閉包便有了理論中的閉包( MDN )和實踐中的閉包( JS 高程)之分。
根據 MDN 的解釋寫個例子:
var a = 1
function fn() {
console.log(a)
}
fn()
複製代碼
函數 fn 和函數 fn 的詞法做用域構成了一個閉包。可是這不是普通的函數嗎?
在《JavaScript 權威指南》中也獲得證明:
從技術的角度講,全部 JavaScript 函數都是閉包
湯姆大叔翻譯的文章中講,實踐中的閉包須要知足如下兩個條件:
什麼是上下文?即函數的執行環境。
什麼是自由變量?即函數的詞法做用域中的變量和函數,而不是函數自己的參數或者局部變量,或者說是所在函數的變量對象中的變量和函數。
這兩點和 JS 高程中講的閉包的解釋不謀而合。
如今寫個符合的例子:
function fn() {
var a = 1
function fn1() {
console.log(a)
}
return fn1
}
var b = fn()
b()
複製代碼
當執行 b 的時候,建立它的執行環境 fn 早已經摧毀,但函數 b 還能訪問到變量 a。
好吧,我有點亂!
要完全明白這個是咋回事,要結合執行環境、活動變量和做用域鏈來看,讓咱們來看看這個例子的執行過程:
環境棧 = [globalContext]
複製代碼
globalContext = {
VO: [global],
Scope: [globalContext.VO],
this: globalContext.VO
}
複製代碼
全局執行環境的做用域鏈
到 fn 的 [[scope]]
屬性fn.[[scope]] = [
globalContext.VO
]
複製代碼
環境棧 = [fnContext, globalContext]
複製代碼
[[scope]]
建立 fn 做用域鏈fnContext = {
AO: {
arguments: {},
scope: undefined,
fn1: reference to function fn1(){}
},
Scope: [AO, globalContext.VO],
this: undefined
}
複製代碼
fnContext 的做用域鏈
到 fn1 的 [[scope]]
屬性fn1.[[scope]] = [
fnContext.AO, globalContext.VO
]
複製代碼
環境棧 = [globalContext]
複製代碼
環境棧 = [
fn1Context,
globalContext
]
複製代碼
[[scope]]
建立 fn1 做用域鏈fn1Context = {
AO: {
arguments: {}
},
Scope: [AO, fnContext.AO, globalContext.VO],
this: undefined
}
複製代碼
環境棧 = [globalContext]
複製代碼
當執行函數 b 的時候,建立它的執行環境 fn 早已摧毀(步驟 6),只留下了它的活動變量 fnContext.AO
於內存中(步驟 5):
fn1Context = {
Scope: [AO, fnContext.AO, globalContext.VO]
}
複製代碼
fnContext.AO
存在 fn1 的做用域鏈中,因此能訪問到fn1 的詞法環境,這便造成了閉包。所以,閉包是變量對象和做用域鏈共同做用的結果。