紅寶書上對閉包的定義:有權訪問另一個函數做用域中變量的函數。html
MDN對閉包的定義是:是可以訪問自由變量的函數。算法
自由變量:是指在當前函數中可使用的(可是既不是arguments也不是本函數定義的局部變量)。瀏覽器
兩個點:閉包
就是說咱們常見的好比內部函數從外部函數返回這種狀態,該內部函數就是閉包。能夠看以下特性中的示例!函數
說明閉包的幾個特性:優化
function outer() {
var date = '11月1日'; function inner(str) { console.log(str + date) } return inner('today is ') } outer() function outer() { var date = '11月1日'; return function () { console.log('today is ' + date) }() } outer() // 上下兩例均返回「today is 11月1日」
function outer() {
var date = '11月1日'; function inner() { console.log('today is ' + date) } return inner } // 如下是拆成分步執行,實際等同於outer()();
// 先執行outer()獲得一個返回值inner,此時outer函數執行完畢,跳出outer這個外層函數
// 而後執行inner(),可是此時依然可用outer定義的變量date var getDate = outer() getDate()
function outer() {
var date = '11月1日'; function inner(newDate) { date = newDate // 將傳入的值替換掉外層的date console.log('today is ' + date) } return inner } var getDate = outer() getDate('191101') // 「today is 191101」
如下例分析:this
var scope = 'global';
function checkscope() { var scope = 'local'; function fun() { return scope; } return fun; } var check = checkscope(); console.log(check()); // 'local'
執行過程:spa
這個流程能夠看到,checkscope執行完畢後是帶着返回值彈出了執行棧的,在fun執行的時候checkscope函數的上下文已經被銷燬了,可是,函數fun執行上下文維護了一個做用域鏈,結構以下:code
funContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
指向關係是:當前做用域 -> checkscope -> 全局,不論checkscope 是否被銷燬,fun函數均可以經過fun的做用域鏈找到它,這是閉包實現的關鍵!htm
有關全局環境下函數嵌套與非嵌套時做用域鏈的指向分析,參考:深刻淺出圖解做用域鏈和閉包
var data = []; for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();
答案很顯然:三、三、3
上面這個題的分析參照個人另外一篇博客分析:let和const,裏面詳細的分析了這個題的過程~
如何才能讓這個題輸出咱們想要的0、一、2呢?
解法一:博客當中給出了使用let的寫法,很簡單隻須要將for中的var替換成let便可
解法二:
還有別的方法嗎?本片將採用閉包的方式解決這個問題~~~回想一下閉包的狀態是什麼?內層函數可使用外層函數定義的變量呀!
因此第一步:咱們在function中return一個新的函數,在內層函數中訪問變量 i。
而後考慮咱們如何才能把當前的i傳到外層function中呢?馬上咱們聯想到利用參數!
第二步:外層函數自執行,將 i 做爲參數傳入外層的function中
所以獲得以下優化後的代碼:
var data = []; for (var i = 0; i < 3; i++) { data[i] = (function (i) { return function () { console.log(i); } })(i) } data[0](); data[1](); data[2]();
結果爲:0、一、2
正是咱們要的結果啦~
解法三:
這裏還能夠改爲咱們常見的定時器寫法:
for (var i = 0; i < 3; i++) { (function (i) { setTimeout(function () { console.log(i) }, 100 * i) })(i) }
解法三其實和解法二的本質相同,都是將變量 i 的值複製給外層function的參數 i ,在函數內部又建立一個用於訪問 i 的匿名函數,這樣每一個函數都有一個 i 的副本,就不會相互影響!
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } checkscope()(); var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); foo();
結論是:第一個代碼段中的scope特定時間後會被回收,第二段代碼的自由變量不會回收
分析:
如今主流瀏覽器的垃圾回收算法是:標記清除。當垃圾回收開始時,從root開始尋找這個對象的引用是否可達,也就是找是否存在相互引用,若是引用鏈斷裂,那麼這個對象就能夠被回收!
對於第一段代碼,checkscope()執行完畢後被彈出執行棧,而且也沒有其餘引用,Root開始查找時不可達,所以閉包引用的自由變量scope過段時間能夠被回收
對於第二段代碼,因爲var foo = checkscope(),checkscope()執行完成後,將foo()執行上下文壓入執行棧,foo()指向堆中的自由變量 f ,對於Root來講可達,所以不會被回收!!
若是想要scope必定能夠被回收,只要加:foo = null;便可!