前言: 對於大多數前端同學來說閉包一直是個很讓人困惑的問題,我自己之前雖說在項目中有意無意的用到但是都沒有刻意的去深入研究它,大部分時間是為了應付面試。後來某一天我突然意識到自己要去認真研究下它,因為知其然而不知其所以然並不應該是學習一種語言的態度,所以我打算寫篇文章嘗試著用我自己的理解去解釋下閉包 ...
前言:
對於大多數前端同學來說閉包一直是個很讓人困惑的問題,我自己之前雖說在項目中有意無意的用到但是都沒有刻意的去深入研究它,大部分時間是為了應付面試。後來某一天我突然意識到自己要去認真研究下它,因為知其然而不知其所以然並不應該是學習一種語言的態度,所以我打算寫篇文章嘗試著用我自己的理解去解釋下閉包。
一、什麼是閉包?
關於閉包不同的人有不同的理解,在javaScript高級編程第三版中給出瞭如下的解釋:閉包是指有權訪問另一個函數作用域中的變數的函數。其實解釋出來說閉包就是一個函數,只不過它可以訪問別的函數內部的變數。單純的看這句話很是抽象 ,我們先看一段代碼:
function fn1() { var a = 1; function fn2() { console.log(a) } } fn1();
這段代碼中我們先聲明瞭一個函數fn1然後在函數內部生明瞭一個函數fn2而我們在fn2中引用了fn1中的變數a ;當我們執行fn1時,這時候閉包就產生了。我們可以藉助谷歌瀏覽器f12調試工具很清楚的看到閉包
在14行打斷點後我們很清楚的看到在內部函數f2內部有一個Closure的對象,這就是我們所說的閉包.此時我們對閉包可以更準確的的解釋:閉包是一個存在內部函數對象里的包含被引用變數的對象。
二、如何產生閉包?
當一個嵌套的內部函數引用了外部函數的變數時,就產生了閉包。上例中 內部函數fn2引用了外部函數f1的內部變數a 因此就產生了閉包。
三、常見的產生閉包的方式
(1)、將函數作為另一個函數的返回值
function add() { var num = 0; var fn2 = function() { num++; console.log(num) } return fn2 } var fn = add(); fn() //1 fn() //2
由於內部函數對象fn2引用了外部函數add內的變數num,所以當add方法執行時就產生了閉包。
(2)、將一個函數當作實參傳入
function showMsg(msg, time) { setTimeout(function() { alert(msg) }, time) } showMsg('hello', 2000)
由於內部匿名函數引用了外部函數showMsg的變數msg 因此產生了閉包
四、閉包的作用
我們接著使用之前的例子:
function add() { var num = 0; var fn2 = function() { num++; console.log(num) } return fn2 } var fn=add(); fn()//1 fn()//2
(1)函數在執行完畢後依然保留內部變數
正常情況下add函數執行完成後,add函數內部的局部變數將會被垃圾回收機制回收,但當我們用fn保留對add內部的函數的引用時 內部函數對象就不會被釋放,而內部函數又引用了num,因此變數num在add函數執行完畢後也不會被釋放,而是繼續存在記憶體中。
(2)訪問函數內部變數
由於js的機制導致我們訪問某一變數時我們只能從內往外部去訪問,但是使用閉包我們就可以訪問某函數內部的局部變數。從上例可以看到,我們在window中調用了add()函數,並且訪問到了add的局部變數 num
五、閉包在實際開發中的應用
正如文章開頭所說,我們在實際的開發過程中總能有意無意的用到閉包,下麵我隨便列舉幾個例子,更好的幫大家理解閉包、
(1)、迴圈便利加監聽
現在我有一個ul 內部4個li,要求點擊每個li的時候彈出其對應的下標,大家很容易想到如下寫法:
let list = document.querySelectorAll('li'); for (var i = 0; i < list.length; i++) { var el = list[i]; el.index = i; el.onclick = function() { alert(el.index) } }
當我們執行的時候發現,點擊的時候彈出的都是3。這是因為i聲明的是一個全局變數,當我們加監聽之前迴圈已經執行完畢了這時候我們得到的i是3,所以每一個el.indexf賦的值都是一樣的,所以每次點擊都是3.我們現需要改進下代碼:
let list = document.querySelectorAll('li'); for (var i = 0; i < list.length; i++) { (function() { var el = list[i]; el.index = i; el.onclick = function() { alert(el.index) } })(i) }
我們在監聽事件外部 包了一個匿名的自執行函數,這時候我們發現都能得到我們期望的結果了。因為for迴圈每此執行時我們將i傳入了匿名函數並賦值給了el.index,而onclick的函數又引用了el.index這個變數,因此產生了閉包,每一個index都將被存下。
(2)、緩存this
var name = 'Jony'; var obj = { name: 'Tom', fn: function() { var _this = this; return function() { return function() { alert(_this.name) } } } } obj.fn()()() //Tom
此例子中,我們obj.fn()得到了一個函數對象,並且此時得this是obj,我們把this賦值給了_this,因此我們執行最終得alert時得到得是Tom
(3)、封裝JS模塊
我們為了防止變數被污染經常回去使用閉包去封裝JS 模塊
(function(window) { var msg = "Hello"; //私有變數 function fn() { return msg.toLowerCase() } function fn2() { return msg.split(''); } window.module = { fn: fn, fn2: fn2 } })(window)
以上就是我個人對於閉包的理解,希望能幫助到正咋子為閉包而困惑的你.
如有錯誤敬請指出!!,與諸君共勉!!