閉包是指有權訪問另外一個函數做用域中的變量的函數。一般是在一個函數的內部建立另外一個函數,那個函數則被稱爲閉包。這個概念很難弄懂,我到如今也是隻知其一;不知其二,可是有幾點是確定的:想要理解什麼是閉包咱們就要知道建立函數開始到函數被執行,這一過程當中到底發生了什麼?這之中又涉及到詞法做用域。git
詞法做用域是一個比較好理解的概念。它是由編譯器在編譯階段就能夠肯定的事情。全局有個全局做用域,每定義一個函數,編譯過程當中就建立一個函數做用域。 而咱們熟知的做用域鏈就是一層套一層的做用域構成的。這裏咱們須要知道一點:做用域(鏈)的做用是在運行階段爲引擎找到存在的變量(等,以上是我本身的理解)github
function bar (name) {
var age = 23
return function () {
alert(name, age)
}
}
bar('Nicholas') // Nicholas 23
複製代碼
以上代碼,bar 函數中有一個 匿名函數 這個匿名函數能夠訪問到 bar 函數中的變量。其實更明顯的作法是下面這樣:緩存
function bar ...
var fn = bar('Nicholas')
fn() // Nicholas 23
複製代碼
上面這段代碼,咱們取得 bar 函數中匿名函數的引用後,垃圾處理機制並未銷燬 bar 函數的活動對象,而讓匿名函數獲取到了 bar 函數的做用域,這就是閉包的用處安全
值得注意的是,閉包無處不在,同時閉包與詞法做用域息息相關,而次發做用域是在爲引擎執行階段 獲取變量引用,要理解這個意思須要理解下面這段經典的一批的代碼:bash
function createFn () {
var fns = []
for (var i = 0; i < 10; i++) {
fns.push(function () {
console.log(i)
})
}
return fns
}
var fns = createFn()
fns.forEach(function (fn) {
fn()
}) // 10 個 10 啦
複製代碼
答案也沒啥稀奇,可是這中間到底發生了什麼?涉及到做用域的概念:做用域其實包含了執行環境的變量對象,而函數的做用域的變量對象就是函數的活動對象,而活動對象就是包含了函數的參數、定義的變量等。 而咱們又知道 js是不存在塊級做用域 的,所以在 createFn 函數中 for 循環中定義的 i 變量,其實保存在 createFn 的函數做用域的變量對象中,所以當定義完了10個函數以後,i 也變成了 10,再調用 函數的時候,會訪問 i,而匿名函數中沒有 i,就向上搜索到 createFn 的做用域,這裏有 i,輸出 i 就輸出了 10。閉包
做用域鏈,就是一堆做用域連起來啦,沒啥好稀奇的。在下一節以前我要強調的是:詞法做用域是詞法做用域,this 是 this,不要試圖在理解 this 的時候,帶入詞法做用域的知識app
以上代碼如何輸出 0 ~ 9?咱們再建立一個閉包,緩存 i 的值而後讓閉包訪問到就能夠了。理解了這個,閉包就差很少理解了。函數
function createFn () {
var fns = []
for (var i = 0; i < 10; i++) {
fns.push(function (i) {
return function () {
console.log(i)
}
}(i))
}
return fns
}
var fns = createFn()
fns.forEach(function (fn) {
fn()
})
複製代碼
閉包能夠用來作什麼?其實能夠作不少事情。從事情的最開始來說吧。ui
- 若是全部的變量都在全局定義,會怎麼樣?會沒辦法定義同名變量,會讓代碼變得雜亂無章,會不知道代碼在作什麼。因而有了函數,咱們使用函數包裝起一段代碼,這樣定義在這段代碼裏的變量,根據詞法做用域的規則,全局是沒辦法訪問到函數裏的變量的。
- 既然定義了一個函數,爲何會有閉包出現?其實閉包徹底能夠定義成另外一個函數扔在全局中,可是頗有可能一個函數會改變另外一個函數之中值(經過調用函數的方式,而非直接改變,由於詞法做用域的關係,一個函數若是不是閉包絕對沒有辦法訪問到另外一個函數的做用域的), 這就很危險了,咱們不肯意讓一些咱們不知道的方式引用了一個函數,而改變另外一個函數,因而咱們把一個函數從全局定義扔到了另外一個函數中去,這樣既減小了全局做用域中的變量個數,又更加安全。這就是閉包的做用。
- 閉包的好處:能夠訪問另外一個函數的做用域!這看來好像沒什麼特別的,可是這偏偏就是閉包最厲害的地方:若是一個函數須要依賴一些其餘函數怎麼辦?咱們徹底能夠將全部的依賴函數統一管理。因而咱們能夠經過閉包寫一個本身的模塊依賴機制!
function ModuleManager () {
// 存放定義的模塊
var modules = {}
// 定義模塊
function define (name, deps, impl) {
for (var i = 0; i < deps.length; i++) {
deps[i] = modules[deps[i]]
}
modules[name] = impl.apply(impl, deps)
}
// 獲取模塊
function get (name) {
return modules[name]
}
return {
define: define,
get: get
}
}
var myModules = ModuleManager()
myModules.define('bar', [], function () {
return {
hello: function (who) {
return 'Hello, I am ' + who
}
}
})
myModules.define('foo', ['bar'], function (bar) {
var name = 'Nicholas'
return {
awesome: function () {
console.log(bar.hello(name).toUpperCase())
}
}
})
var bar = myModules.get('bar')
var foo = myModules.get('foo')
console.log(bar.hello('Bob'))
foo.awesome()
複製代碼
爲何以上代碼沒有使用原型對象,類的方式去寫,就是爲了展現閉包的魔力:modules 能被 define 函數和 get 函數時刻訪問到,因而咱們就獲得了一個模塊管理的入口,這就是閉包的一個做用,咱們能夠本身寫出不少關於閉包的東西,咱們要擁抱閉包,而不是害怕它原理它。this
持續跟新在github