看了知乎上的話題 如何才能通俗易懂的解釋javascript裏面的‘閉包’?,受到一些啓發,所以結合實例將回答中幾個精要的答案作一個簡單的分析以便加深理解。javascript
1. "閉包就是跨做用域訪問變量。" html
【示例一】java
var name = 'wangxi' function user () { // var name = 'wangxi' function getName () { console.log(name) } getName() } user() // wangxi
在 getName 函數中獲取 name,首先在 getName 函數的做用域中查找 name,未找到,進而在 user 函數的做用域中查找,一樣未找到,繼續向上回溯,發如今全局做用域中存在 name,所以獲取 name 值並打印。這裏很好理解,即變量都存在在指定的做用域中,若是在當前做用中找不到想要的變量,則經過做用域鏈向在父做用域中繼續查找,直到找到第一個同名的變量爲止(或找不到,拋出 ReferenceError 錯誤)。這是 js 中做用域鏈的概念,即子做用域能夠根據做用域鏈訪問父做用域中的變量,那若是相反呢,在父做用域想訪問子做用域中的變量呢?——這就須要經過閉包來實現。瀏覽器
【示例二】閉包
function user () { var name = 'wangxi' return function getName () { return name } } var userName = user()() console.log(userName) // wangxi
分析代碼咱們知道,name 是存在於 user 函數做用域內的局部變量,正常狀況下,在外部做用域(這裏是全局)中是沒法訪問到 name 變量的,可是經過閉包(返回一個包含變量的函數,這裏是 getName 函數),能夠實現跨做用域訪問變量了(外部訪問內部)。所以上面的這種說法完整的應該理解爲:函數
閉包就是跨做用域訪問變量 —— 內部做用域能夠保持對外部做用域中變量的引用從而使得(更)外部做用域能夠訪問內部做用域中的變量。(仍是不理解的話看下一條分析)post
2. "閉包:在爺爺的環境中執行了爸爸,爸爸中返回了孫子,原本爸爸被執行完了,爸爸的環境應該被清除掉,可是孫子引用了爸爸的環境,致使爸爸釋放不了。這一坨就是閉包。簡單來說,閉包就是一個引用了父環境的對象,而且從父環境中返回到更高層的環境中的一個對象。"url
這個怎麼理解呢?首先看下方代碼:spa
【示例三】code
function user () { var name = 'wangxi' return name } var userName = user() console.log(userName) // wangxi
問:這是閉包嗎?
答:固然不是。首先要明白閉包是什麼。雖然這裏形式上看好像也是在全局做用域下訪問了 user 函數內的局部變量 name,可是問題是,user 執行完,name 也隨之被銷燬了,即函數內的局部變量的生命週期僅存在於函數的聲明週期內,函數被銷燬,函數內的變量也自動被銷燬。
可是使用閉包就相反,函數執行完,生命週期結束,可是經過閉包引用的外層做用域內的變量依然存在,而且將一直存在,直到執行閉包的的做用域被銷燬,這裏的局部變量纔會被銷燬(若是在全局環境下引用了閉包,則只有在全局環境被銷燬,好比程序結束、瀏覽器關閉等行爲時纔會銷燬閉包引用的做用域)。所以爲了不閉包形成的內存損耗,建議在使用閉包後手動銷燬。仍是上面示例二的例子,稍做修改:
【示例四】
function user () { var name = 'wangxi' return function getName () { return name } } var userName = user()() // userName 變量中始終保持着對 name 的引用 console.log(userName) // wangxi userName = null // 銷燬閉包,釋放內存
【爲何 user()() 是兩個括號:執行 user() 返回的是 getName 函數,要想得到 name 變量,須要對返回的 getName 函數執行一次,因此是 user()()】
根據觀點2,分析一下代碼:在全局做用域下建立了 userName 變量(爺爺),保存了對 user 函數最終返回結果的引用(即局部變量 name 的值),執行 user()()(爸爸),返回了 name(孫子),正常狀況下,在執行了 user()() 以後,user 的環境(爸爸)應該被清除掉,可是由於返回的結果 name(孫子)引用了爸爸的環境(由於 name 原本就是存在於 user 的做用域內的),致使 user 的環境沒法被釋放(會形成內存損耗)。
那麼【"閉包就是一個引用了父環境的對象,而且從父環境中返回到更高層的環境中的一個對象。"】如何理解?
咱們換個說法:若是一個函數引用了父環境中的對象,而且在這個函數中把這個對象返回到了更高層的環境中,那麼,這個函數就是閉包。
仍是看上面的例子:
getName 函數中引用了 user(父)環境中的對象(變量 name),而且在函數中把 name 變量返回到了全局環境(更高層的環境)中,所以,getName 就是閉包。
3. "JavaScript中的函數運行在它們被定義的做用域裏,而不是它們被執行的做用域裏。" ——《JavaScript權威指南》
這句話對閉包中對變量的引用的理解頗有幫助。咱們看下面的例子:
var name = 'Schopenhauer' function getName () { console.log(name) } function myName () { var name = 'wangxi' getName() } myName() // Schopenhauer
若是執行 myName() 輸出的結果和你想象的不同,你就要再回去看看上面說的這句話了,
JavaScript 中的函數運行在它們被定義的做用域裏,而不是它們被執行的做用域裏
執行 myName,函數內部執行了 getName,而 getName 是在全局環境下定義的,所以儘管在 myName 中定義了變量 name,對getName 的執行並沒有影響,getName 中打印的依然是全局做用域下的 name。
咱們稍微改一下代碼:
var name = 'Schopenhauer' function getName () { var name = 'Aristotle' var intro = function() { // 這是一個閉包 console.log('I am ' + name) } return intro } function showMyName () { var name = 'wangxi' var myName = getName() myName() } showMyName() // I am Aristotle
結果和你想象的同樣嗎?結果留做聰明的你本身分析~
以上就是對 js 中閉包的理解,若是有誤,歡迎指正。最後引用一段知乎問題下關於閉包概念的一個回答。
(做者:蕭瀟 連接:https://www.zhihu.com/question/34547104/answer/197642727)
什麼是閉包?
簡單來講,閉包是指能夠訪問另外一個函數做用域變量的函數,通常是定義在外層函數中的內層函數。
爲何須要閉包?
局部變量沒法共享和長久的保存,而全局變量可能形成變量污染,因此咱們但願有一種機制既能夠長久的保存變量又不會形成全局污染。
特色
什麼時候使用?
變量既想反覆使用,又想避免全局污染
如何使用?
【參考】