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