閉包幾乎是通往高級前端工程師所必須經(jīng)過的一個門檻。在這方面看過很多資料,感覺許多文章都只是提到了閉包的實現(xiàn),而沒有涉及閉包的原理。
在談閉包之前需要先了解幾個相關的概念:
1、變量作用域:全局變量擁有全局作用域,在js代碼的任何地方都可見。在函數(shù)內(nèi)部聲明的變量只在該函數(shù)體內(nèi)可見,被稱為局部變量。
2、函數(shù)作用域:在函數(shù)內(nèi)聲明的所有變量在該函數(shù)體內(nèi)始終是可見的。
3、作用域鏈:作用域鏈是一個對象。作用域鏈中定義了該作用域內(nèi)的變量,它保證了該作用域中變量、函數(shù)的有序訪問。
4、作用域鏈的創(chuàng)建:定義一個函數(shù)時,會保存一個作用域鏈。調(diào)用一個函數(shù)時,會創(chuàng)建一個新的對象來存儲其局部變量,并將該對象添加至保存的作用域鏈上,同時創(chuàng)建一個新的更長的表示函數(shù)調(diào)用作用域的鏈。對于嵌套函數(shù)來說,每調(diào)用一次外部函數(shù),都會創(chuàng)建一個新的作用域鏈,所以每次調(diào)用外部函數(shù),雖然其內(nèi)部代碼相同,但是調(diào)用的作用域鏈是不同的。
5、垃圾回收機制:js具有自動垃圾回收機制,會定期把不再使用的變量銷毀,釋放其占用的內(nèi)存。(只會銷毀局部變量,全局變量的生命周期只有在頁面或瀏覽器關閉時才會結束)
閉包的原理:函數(shù)的執(zhí)行依賴于函數(shù)定義時的作用域鏈,即js函數(shù)執(zhí)行時用的作用域鏈是該函數(shù)定義時創(chuàng)建的作用域鏈。大多數(shù)時候定義函數(shù)時的作用域鏈在函數(shù)執(zhí)行時仍然有效。但是如果定義函數(shù)時的作用域鏈和執(zhí)行函數(shù)時的作用域鏈不同時,就會出現(xiàn)問題。即當一個函數(shù)中嵌套了另一個函數(shù),并把嵌套的函數(shù)對象作為返回值返回時,就會產(chǎn)生這種情況。


第一個例子很容易理解。但是第二個,可以看到當外部函數(shù)把嵌套函數(shù)作為返回值返回時,其執(zhí)行結果仍然是局部變量的值,而不是全局變量的值。這就是因為函數(shù)執(zhí)行時用到的作用域鏈,其實是函數(shù)定義時創(chuàng)建的。不管該函數(shù)在何時何地執(zhí)行,它通過作用域鏈最先找到的變量就是相同外部函數(shù)中定義的變量。而閉包正是利用這種特性實現(xiàn)的:閉包可以捕捉到局部變量或參數(shù),并一直保存下來,使其不會被當成垃圾回收。
閉包和垃圾回收的關系:調(diào)用函數(shù)時,會創(chuàng)建一個對象來保存其局部變量,并且把這個對象添加到作用域鏈上。當函數(shù)返回時(即執(zhí)行完成了),就會從作用域鏈中把綁定局部變量的對象刪除,這個對象就會被當作垃圾回收。但是如果定義了嵌套函數(shù),那么嵌套函數(shù)也會有自己相應的作用域鏈,它與外部函數(shù)一樣,把其局部變量保存到一個對象上,如果嵌套函數(shù)是作為返回值返回的或者存儲在某處的屬性里,那么就會有一個外部引用指向這個嵌套函數(shù),那么外部函數(shù)就不會被當作垃圾回收,它在作用域鏈中綁定局部變量的對象也不會被回收。
閉包的應用場景:實現(xiàn)私有成員;保護命名空間;避免污染全局變量;可以使變量長期駐留在內(nèi)存中。這是在網(wǎng)上找的,不是特別好理解。其實可以參考jQuery的源碼,它就是將所有的代碼封裝到了一個閉包里邊的。下邊是我之前寫的一段代碼,再根據(jù)閉包進行的改寫:


在改寫后的代碼中,定義了2個閉包,這2個閉包共享同一個變量defaultSize??梢钥闯鍪褂瞄]包對代碼的封裝,避免全局變量的污染有很大的作用。
使用閉包的注意事項:當然閉包也不是十全十美,它可以把局部變量長期保存到內(nèi)存中,不被當作垃圾回收,這種特性如果使用不當,很容易會造成內(nèi)存泄漏。建議還是多看看其它比較成熟的框架是怎么使用閉包的,會對提高個人代碼的質(zhì)量有很大的幫助。