先看一個簡單的需求:瀏覽器
這真的是一個簡單的需求, 咱們能夠立馬寫出來閉包
var counter = 0 function addCounter(){ counter++ console.log(counter); } addCounter() // 1 addCounter() // 2 addCounter() // 3
可是你有沒有想過: 爲何 counter
能夠一直被累加 ?ide
這裏就牽扯到了js中的 垃圾回收機制函數
所謂的垃圾回收, 就是肯定哪一個變量再也不使用, 而後釋放它佔用的內存, 這是一個週期性的檢測學習
可是對於全局變量而言, 垃圾回收機制並不知道它何時再也不使用, 所以也就沒法釋放其佔用的內存, 全局變量也就會一直被保存在內存當中, 這也就是咱們能夠一直使用它的緣由prototype
其實從執行上下文的角度來看, 全局變量的聲明是在全局上下文中進行的, 被一直保存在GO中, 而全局上下文是在咱們關閉瀏覽器時纔會被銷燬, 所以其餘函數就能夠經過其保存的上下文環境 (也就是做用域鏈), 到GO中引用countercode
這樣, 第一個問題咱們就解決了blog
好, 咱們再來思考另一個問題, 若是另一個函數也用到了 counter
, 或者另一個開發者聲明瞭一個一樣的全局變量 counter
,那麼這勢必會影響上面累加器的運做, 由於 counter
的值被其餘函數 / 變量聲明 改變了, 這顯然是咱們所不但願的ip
那既然不想counter被其餘因素所改變, 那咱們把counter做爲累加器的內部變量不就好了麼, 這樣只有函數本身能用, 行嗎? 顯然不行內存
由於函數在執行完畢後, 會銷燬其所在的上下文環境, 保存在AO中的變量會被垃圾回收機制釋放, 對於沒法一直保存在內存中的變量, 咱們顯然沒法一直對他進行操做
那有沒有辦法能夠解決這個需求: 咱們既想把 counter 聲明爲局部變量, 又讓他一直保留在內存中呢 ?
答案是能夠的, 這就是咱們須要閉包的緣由
我的理解:
咱們知道: 執行上下文是一個運行時環境, 當函數執行完畢, 函數上下文會被釋放, 局部變量天然也就沒法一直保留
而閉包的存在, 就是幫助咱們把這些會被釋放的上下文保存到內存中,使其一直存在
MDN中給出閉包的以下定義:
示例
function fn() { var count = 0 function addCount() { count++ } addCount() } fn()
上面有沒有造成閉包呢? 咱們根據MDN的定義來分析一下:
addCount
是否是一個函數? 是addCount
對其周圍狀態有沒有引用 ? 有, 引用了所處上下文環境中的 count
既然造成了閉包, 那能不能完成咱們上面的需求呢: **咱們既想把 counter 聲明爲局部變量, 又讓他一直保留在內存中 **
咱們來接着調用addCount
函數, 看三次調用之後, count
的值是否是3
注意看 watch中count的變化
經過演示咱們發現, count
並無一直被保留在內存中, 而是每次調用都被初始化爲了0
這顯然沒法完成咱們的需求
ECMAScript中, 給出以下定義:
從理論角度:全部的函數都是閉包。由於它們都在建立的時候就將上層上下文的數據保存起來了。哪怕是簡單的全局變量也是如此,由於函數中訪問全局變量就至關因而在訪問自由變量,這個時候使用最外層的做用域。
從實踐角度:如下函數纔算是閉包:
根據ECMAScript給出的定義, 咱們再來對比MDN給出的定義, 咱們發現, MDN中對閉包的定義其實就是ECMAScript中的閉包的理論角度,
但這對咱們的需求並無實際的做用, 那咱們再來看 從實際角度出發給出的定義
示例
function fn() { var count = 0 function addCount() { count++ } return addCount } var add = fn() add() add() add()
注意: 與上面代碼不一樣的是, 咱們此次將addCount函數做爲返回值傳遞到外部
注意此時watch中count的變化
咱們能夠清晰的看到, 內部變量count
, 在函數外部被一直引用, 並且實現了值的累加 !
也就是說, count
被一直保留在了內存之中, 這也真正實現了咱們上面所一直所提的需求:
其實說了這麼多, 可能仍是比較抽象, 咱們先回顧如下閉包的理解:
咱們知道: 執行上下文是一個運行時環境, 當函數執行完畢, 函數上下文會被釋放, 局部變量天然也就沒法一直保留
而閉包的存在, 就是幫助咱們把這些會被釋放的上下文保存到內存中,使其一直存在
咱們舉個例子:
假設我家院子 (內存)裏有一個大水缸 (不能釋放的內存), 水缸裏面有滿滿的水 (全局變量), 這個水過路口渴的人 (其餘函數等)均可以取來喝,可是不一樣的人都來喝, 不就把我家的水給 污染 了麼, 何況這是我家, 大家怎麼能想喝就喝呢, 因而我想出了這樣一個辦法:
我先把水(此時變成局部變量)都移到紙箱(外層函數)裏, 可是紙箱存不住水 (函數被釋放)呀, 因而我又立刻把這些水都灌進水瓶 (內層函數)裏, 而後把這些水瓶放在紙箱裏 (函數嵌套), 這樣咱們的水得以保存, 可是別人也想喝怎麼辦, 那就按照個人說明去紙箱裏拿水瓶 (暴露給外部的引用, 到此造成閉包), 而且你只能經過暴露出的水瓶來喝水
後來家家裝了自來水, 你們也都不須要來我家喝水了, 因而我就把全部的水瓶丟掉(清除引用, 釋放閉包的內存),
綜上:
怎麼理解閉包:
咱們知道: 執行上下文是一個運行時環境, 當函數執行完畢, 函數上下文會被釋放, 局部變量天然也就沒法一直保留
而閉包的存在, 就是幫助咱們把這些會被釋放的上下文保存到內存中,使其一直存在
什麼纔算閉包:
從實踐角度:如下函數纔算是閉包:
1. 訪問函數內部的變量, 也就是上面一直提的需求
function fn() { var count = 0 function addCount() { count++ } return addCount } var add = fn() add()
2. 定時器傳值, 本質與上面一致
function func(param) { return function () { alert(param) } } var f1 = func(1) setTimeout(f1, 1000)
function isType(type) { return function (target) { return `[object ${type}]` === Object.prototype.toString.call(target) } } const isArray = isType('Array') console.log(isArray([1, 2, 3])); // true console.log(isArray({}));