這是篇文章主要是講一下對閉包這一律唸的理解。討論閉包以前,咱們先從一個經典的例子提及es6
// 程序1
var arr = []
for(var i = 0; i < 3; i++){
arr[i] = function () {
console.log(i)
}
}
arr[0]() // 3
arr[1]() // 3
arr[2]() // 3
複製代碼
你們都知道,這段代碼最終輸出都爲3。由於函數調用的時候循環已經結束了因此 i 等於3,更爲重要的是,es6以前沒有塊做用域,變量 i 的做用域不在for循環中,而在for循環以外。若是咱們想要看到輸出結果依次爲0,1,2,就得用到閉包了。不然,除非咱們能在每次循環的過程當中調用函數,由於只有在循環進行的過程當中 i 纔會處於0,1,2的狀態。好比像這樣:bash
// 程序2
for(var i = 0; i < 3; i++){
(function () {
console.log(i)
})()
}
// 0
// 1
// 2
複製代碼
必需要在循環進行時調用,像下面這樣都不行,由於這樣和程序1實際上是同樣的,函數調用時i已經等於3了。閉包
// 程序3
for(var i = 0; i < 3; i++){
setTimeout(function () {
console.log(i)
}, 0)
}
// 3
// 3
// 3
複製代碼
閉包主要就是用來解決這樣的問題, 它讓函數能夠訪問到函數所被建立時的上下文環境,不論這個函數在何時被調用。 因此閉包產生的條件有兩個,一是函數能經過變量做用域規則訪問到它被建立時的上下文環境,例如程序1,函數只是簡單的訪問了外部的變量 i,嚴格上講不算閉包。二是函數在其它地方執行時,函數依然可以記住並訪問到它所被定義時的上下文環境,咱們使用閉包來對程序1進行修改:函數
// 程序4
var arr = []
for(var i = 0; i < 3; i++){
(function () {
var j = i
arr[i] = function () {
console.log(j)
}
})()
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
複製代碼
不一樣的是此次增長了當即執行函數並在裏面定義變量 j ,咱們能夠把當即執行函數稱爲 fn 。每次循環都會建立一個以當即執行函數爲做用域的變量 j ,原來在程序1中函數訪問的是外部變量 i ,如今訪問的是fn這一閉包中的變量 j 。fn執行結束時 j 本應被回收,可是因爲該做用域內還定義了一個內部訪問了變量 j 的函數,該函數在將來可能被執行,因此 j 被「記住」了,也就是做用域鏈被保存了。咱們能夠把fn稱爲一個閉包,閉包內能夠定義函數而且這些函數能夠訪問閉包中定義的變量,例如:ui
function fn(){
var a = 1;
return function(){
console.log(a)
}
}
var module = fn()
module() // 1
複製代碼
fn返回的函數經過閉包可以訪問到 a。spa
最後總結一下,閉包的主要做用在於「記住」函數定義是的某些外部變量,以供函數調用時能夠訪問。code