javascript
JavaScript夯实基础系列(二):闭包
??在JavaScript中函數(shù)是一等公民。所謂一等公民是指函數(shù)跟其他對象一樣,很普通,可以進行把函數(shù)存在數(shù)組中、作為參數(shù)傳遞、賦值給變量等操作。當函數(shù)作為另一個函數(shù)的返回值在外部調(diào)用時,跟該函數(shù)在函數(shù)內(nèi)部調(diào)用時可訪問的詞法作用域一樣,這種現(xiàn)象被稱為閉包。
一、什么是閉包
??閉包的定義有很多,比如:閉包是指有權(quán)訪問另外一個函數(shù)作用域中變量的函數(shù)。或者更本質(zhì)的定義:函數(shù)對象可以通過作用域鏈關(guān)聯(lián)起來,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),也就是說函數(shù)變量可以隱藏于作用域鏈之內(nèi),看上去是函數(shù)將變量“包裹”了起來。個人比較傾向于一種通俗的定義:閉包就是函數(shù)能夠記住并訪問它的詞法作用域,即使當這個函數(shù)在它的詞法作用域外執(zhí)行時。
??如下代碼所示,執(zhí)行test函數(shù)返回print函數(shù),在全局作用域下執(zhí)行print函數(shù),print函數(shù)卻能記住自己的作用域,能夠引用其在定義時的外層函數(shù)test的局部變量。
??由于JavaScript中沒有塊級作用域的概念,因此常常用立即執(zhí)行函數(shù)(IIFE)來模擬塊級作用域。
??從學術(shù)意義上來講,JavaScript中的每個函數(shù)都是閉包:它們都是對象,它們都關(guān)聯(lián)到作用域鏈。但是從閉包可以在詞法作用域外調(diào)用也能訪問詞法作用域的角度來說,IIFE并不是閉包。如下代碼所示:在函數(shù)test執(zhí)行時,查找變量a是沿著作用域鏈逐級查詢的,并不能體現(xiàn)閉包的特性。
(function IIFE(){var a = 1;function test () {console.log(a)}test() // 1 })(); 復制代碼二、閉包的原理
??當某個函數(shù)執(zhí)行時,先復制其外層函數(shù)的作用域鏈(如果函數(shù)是在全局環(huán)境中則作用域鏈中只有一個全局對象的引用),賦值給一個特殊的內(nèi)部屬性(即[[Scope]])。然后使用this、arguments和其它命名參數(shù)的值來初始化函數(shù)的變量對象,最后將該變量對象的引用加入到該函數(shù)的作用域鏈中。
??當函數(shù)執(zhí)行完之后,函數(shù)的作用域鏈上的會被刪除,相應的變量對象沒有了作用域鏈的引用就會被當做垃圾回收掉。但是閉包的情況卻不一樣,函數(shù)雖然執(zhí)行完畢,但是函數(shù)返回了一個內(nèi)部函數(shù)出去,該內(nèi)部函數(shù)的作用域鏈上擁有對該函數(shù)變量對象的引用,因此函數(shù)雖然執(zhí)行完畢,但該函數(shù)的變量對象并沒有被銷毀,依然可以通過返回的內(nèi)部函數(shù)來訪問該函數(shù)變量對象上的變量。
??需要特別注意的是:閉包只能取得包含函數(shù)中任何變量的最后一個值。在for循環(huán)中定義函數(shù)表達式尤其能體現(xiàn)出這一點。如下代碼所示,我們希望test函數(shù)返回的函數(shù)數(shù)組中存放的是可以打印其下標的函數(shù),但是結(jié)果卻是全部數(shù)字10。原因在于我們錯誤的認為每次循環(huán)時都會對i進行一次復制,事實上嵌套的函數(shù)不會將作用域內(nèi)的私有成員復制一份,也不會對所綁定的變量生成靜態(tài)快照。test函數(shù)返回的函數(shù)數(shù)組中引用的都是同一個變量i,變量i被共享,循環(huán)結(jié)束時i的值為10,所以執(zhí)行函數(shù)數(shù)組中的任意函數(shù)結(jié)果都是打印出數(shù)字10。
??我們對代碼加以改進,來避免數(shù)據(jù)共享的情況發(fā)生。在下面代碼中,并不是直接將閉包賦值給數(shù)組,而是定義了函數(shù)temp,將執(zhí)行temp函數(shù)后的返回值賦給數(shù)組。因為函數(shù)參數(shù)是按值傳遞的,所以每次調(diào)用temp時會復制一份實參i的副本,函數(shù)數(shù)組中保存的函數(shù)都有各自的變量i不同時間段的副本,打破了原本共享數(shù)據(jù)i的情況,因此能夠返回各自不同的值。
三、閉包的用途
??閉包在JavaScript代碼中無所不在,主要應用于模塊模式以及函數(shù)式編程中的柯里化。
1、模塊模式
??在ES6之前,JavaScript中并沒有定義用以支持模塊的語言結(jié)構(gòu),但是可以利用閉包很輕松的實現(xiàn)代碼模塊化。在函數(shù)中定義的變量是函數(shù)私有的,在函數(shù)之外不能直接訪問以及修改函數(shù)內(nèi)部的變量,但是通過函數(shù)返回的內(nèi)部函數(shù)能夠訪問這些變量,返回的內(nèi)部函數(shù)如同暴露在外界的共有接口一樣,這種模式被稱為模塊模式。如下代碼所示:
??在模塊模式中,模塊返回值可以是一個對象,也可以僅僅是一個內(nèi)部函數(shù)。模塊只是一個函數(shù),所以它可以接收參數(shù)。從上面的代碼可以看出函數(shù)每次執(zhí)行返回的閉包是獨立的,相互不影響。一般模塊在使用的時候采用單例模式,可以用IIFE來實現(xiàn),如下代碼所示:
??綜上所述,模塊要求兩個關(guān)鍵性質(zhì):1、作為模塊的函數(shù)被調(diào)用執(zhí)行。2、該函數(shù)的返回值至少用于一個內(nèi)部函數(shù)的引用。
2、柯里化
??柯里化是指把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。柯里化的好處在于提高了適用性,能夠?qū)崿F(xiàn)參數(shù)復用的效果。如下代碼所示:
??_test函數(shù)利用函數(shù)閉包來實現(xiàn)柯里化的效果,每次調(diào)用的函數(shù)閉包能夠訪問上次傳入的參數(shù)并訪問。例如lodash等庫封裝了通用柯里化的函數(shù),傳入一個普通函數(shù),返回一個同等功能的柯里化函數(shù),這一部分會在本系列的后續(xù)文章詳述。
四、總結(jié)
??閉包就是函數(shù)能夠記住并訪問它的詞法作用域,即使當這個函數(shù)在它的詞法作用域外執(zhí)行時。函數(shù)執(zhí)行完畢后,相應的變量對象沒有作用域鏈的引用就會被當做垃圾被回收,但是如果有閉包情況會變得不一樣,閉包的作用域鏈依然對外部函數(shù)的變量對象保持引用,因此外部函數(shù)的變量對象不會被銷毀,閉包依然能夠訪問外部函數(shù)的變量。
??在JavaScript中沒有模塊的語法(ES6之前),閉包可以用來實現(xiàn)模塊模式。在函數(shù)式編程中,柯里化十分常見,可以利用閉包來實現(xiàn)柯里化。
如需轉(zhuǎn)載,煩請注明出處:www.cnblogs.com/lidengfeng/…
轉(zhuǎn)載于:https://juejin.im/post/5c8f602ce51d4566cf262fef
總結(jié)
以上是生活随笔為你收集整理的JavaScript夯实基础系列(二):闭包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: liunx学习
- 下一篇: 好程序员web前端分享使用JavaScr