詳細(xì)聊聊閉包在js中充當(dāng)著什么角色
什么是閉包
開(kāi)篇明義,概念先行。閉包是什么,為什么說(shuō)在js中處處充滿了閉包。
閉包就是函數(shù)有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量,此函數(shù)和被引用的變量一起構(gòu)成了閉包
文字描述文縐縐的難以理解,看一下代碼就能夠一目了然了
function test() { var a = 1 var b = function() { console.log(a) } return b }
在上面的代碼例子中,變量 a 處于函數(shù) test 的作用域中,但是函數(shù) b 中可以對(duì)變量 a 進(jìn)行訪問(wèn)。
套用閉包的概念,也就是函數(shù) b 有權(quán)訪問(wèn)函數(shù) test 作用域中的變量 a,此時(shí)函數(shù) b 與變量 a 就形成了一個(gè)閉包。
看了上面的例子,大家是否恍然大悟,這不就是我們?cè)诖a中經(jīng)常寫(xiě)的嗎。所以說(shuō)js中處處充滿了閉包。
如何觀察閉包
如果一開(kāi)始我們對(duì)于閉包的認(rèn)識(shí)還不是很深刻,我們?cè)趺粗涝诖a中寫(xiě)出了一個(gè)閉包呢?一招教你找出閉包
function test() { let a = 1 return function test1() { debugger console.log(a) } } test()()
如上的一段代碼,在執(zhí)行到 debugger 關(guān)鍵字的時(shí)候,我們可以打開(kāi)瀏覽器的開(kāi)發(fā)者調(diào)制工具,此時(shí)我們可以從調(diào)用棧中看到 Closure 的字樣,這就是代表我們寫(xiě)出了一個(gè)閉包啦
閉包的錯(cuò)誤認(rèn)識(shí)
說(shuō)完了閉包的概念,再來(lái)說(shuō)說(shuō)可能大家會(huì)對(duì)閉包產(chǎn)生的一些錯(cuò)誤認(rèn)識(shí)。
1.閉包的產(chǎn)生需要使用 return 暴露出去
首先從閉包的概念上來(lái)看就沒(méi)有說(shuō)到閉包需要暴露到函數(shù)外才叫閉包,而是只要引用了不屬于當(dāng)前函數(shù)作用域中的變量就已經(jīng)產(chǎn)生閉包了。
為什么會(huì)有這樣的錯(cuò)誤認(rèn)識(shí),是因?yàn)槲覀兪褂瞄]包引用了外部作用域中的變量,一般是為了將這個(gè)變量或者是這個(gè)函數(shù)暴露出去,讓我們?cè)谕獠恳部梢栽L問(wèn)到這個(gè)變量或者函數(shù),也就是說(shuō)將閉包暴露到函數(shù)外部只是我們的業(yè)務(wù)需求,而不是閉包的必要條件。
2.閉包會(huì)導(dǎo)致內(nèi)存泄漏
首先我們要知道為什么閉包會(huì)導(dǎo)致內(nèi)存泄漏,是因?yàn)槲覀儗㈤]包暴露到函數(shù)外部的時(shí)候,閉包內(nèi)部仍然引用著其外部作用域中的變量,導(dǎo)致外部作用域中的變量無(wú)法被垃圾回收機(jī)制回收,如果循環(huán)引用閉包的話就容易造成內(nèi)存泄漏的現(xiàn)象。但這是由于我們?cè)谑褂瞄]包過(guò)程中所引起的,而不是閉包本身的性質(zhì)所決定的,因此說(shuō)閉包一定會(huì)導(dǎo)致內(nèi)存泄漏是不嚴(yán)謹(jǐn)?shù)摹?/p>
(另外在 IE9 之后也對(duì)瀏覽器的垃圾回收機(jī)制做了優(yōu)化,現(xiàn)在已經(jīng)不容易導(dǎo)致內(nèi)存泄露了)
閉包導(dǎo)致的問(wèn)題
作為 js 中八大陷阱之一的循環(huán)陷阱,就是由于閉包引起的
for (var i = 0; i < 4; i++) { setTimeout(() => { console.log(i) }, 1000) } // 4, 4, 4, 4
執(zhí)行以上代碼,會(huì)發(fā)現(xiàn) 1s 之后打印了 4個(gè) 4,為什么不是打印 0, 1, 2, 3,就是因?yàn)?setTimeout 中的回調(diào)函數(shù)是一個(gè)閉包,引用了外部作用域中的 i 變量,但是 i 只有一個(gè),并不會(huì)在每個(gè)回調(diào)中生成新的 i,因此在 1s 后打印的時(shí)候訪問(wèn)的是同一個(gè)作用域中的 i 變量,因此打印的結(jié)果就是 4個(gè) 4
如何解決以上問(wèn)題,有兩個(gè)方法:
- 一種是使用 es6 的 let 語(yǔ)法生成塊級(jí)作用域,這樣每個(gè)塊級(jí)作用域中的 i 變量就不是指向同一個(gè) i 變量,不會(huì)對(duì)彼此產(chǎn)生影響
for (let i = 0; i < 4; i++) { setTimeout(() => { console.log(i) }, 1000) } // 0, 1, 2, 3
- 一種是使用立即執(zhí)行函數(shù),每個(gè)立即執(zhí)行函數(shù)中的變量 i 都是當(dāng)前外部變量 i 的一個(gè)快照
for (let i = 0; i < 4; i++) { (function(i) { setTimeout(() => { console.log(i) }, 1000) })(i) } // 0, 1, 2, 3
閉包的使用場(chǎng)景
說(shuō)了這么多閉包的性質(zhì),甚至閉包還會(huì)引發(fā)循環(huán)陷阱這么重大的問(wèn)題,那么閉包到底有什么用?面試官問(wèn)到的時(shí)候總不能說(shuō) js 處處都是閉包,所以 js 到處都是閉包的使用場(chǎng)景吧。那么我們就來(lái)說(shuō)說(shuō)閉包的幾個(gè)經(jīng)典使用場(chǎng)景
1. 單例模式
var CreateSingleton = (function() { var instance = null var CreateSingleton = function() { if (instance) return instance return instance = this } return CreateSingleton })()
單例模式是設(shè)計(jì)模式的一種,目的是為了保證全局中只有一個(gè)實(shí)例對(duì)象,上述代碼利用 instance 創(chuàng)建一個(gè)閉包。單例模式在組件庫(kù)保證全局中只有一個(gè)彈窗組件尤其好用。
2. 函數(shù)柯里化
柯里化是將一個(gè)多參數(shù)的函數(shù)轉(zhuǎn)化成幾個(gè)單參數(shù)的函數(shù)嵌套的形式,例如: function test(a, b, c) => function test(a)(b)(c)
function currying(fn, args) { var _this = this var len = fn.length var args = args || [] return function() { var _args = Array.prototype.slice.call(arguments) Array.prototype.push.apply(args, _args) if(_args.length < len) { return currying.call(this, fn, _args) } return fn.apply(this, _args) } }
3. 與立即執(zhí)行函數(shù)配合使用完成類(lèi)庫(kù)的封裝
閉包往往配合著立即執(zhí)行函數(shù)來(lái)一起使用,能夠發(fā)揮出強(qiáng)大的效果。因此,往往很多人容易被誤導(dǎo),認(rèn)為閉包與立即執(zhí)行函數(shù)之間有什么關(guān)系,甚至認(rèn)為立即執(zhí)行函數(shù)就是閉包。但這種認(rèn)識(shí)其實(shí)是錯(cuò)誤的,立即執(zhí)行函數(shù)與閉包之間沒(méi)有任何關(guān)系。
在 jQuery 盛行的年代,各類(lèi)規(guī)范百花爭(zhēng)艷,其中 umd 規(guī)范能夠兼容多種環(huán)境,主要在于其 umd 頭部結(jié)構(gòu)的實(shí)現(xiàn)
(function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } })( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {})
以上這種代碼的寫(xiě)法使用過(guò) jQuery 開(kāi)發(fā)的人應(yīng)該非常熟悉吧。
4. 保存私有變量
在實(shí)際開(kāi)發(fā)過(guò)程中,我們有時(shí)候需要對(duì)于計(jì)算結(jié)果進(jìn)行緩存,或者是保存私有變量而不被外部訪問(wèn)到,就可以使用閉包來(lái)實(shí)現(xiàn)。
另外,在目前流行的兩大前端框架 Vue 和 React 中其實(shí)也大量用到了閉包進(jìn)行相關(guān)功能的實(shí)現(xiàn),具體大家可以自己翻翻源碼啦~
總結(jié)
到此這篇關(guān)于閉包在js中充當(dāng)著什么角色的文章就介紹到這了,更多相關(guān)js中的閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Bootstrapvalidator校驗(yàn)、校驗(yàn)清除重置的實(shí)現(xiàn)代碼(推薦)
這篇文章給大家介紹了bootstrapvalidator校驗(yàn)、校驗(yàn)清除重置的實(shí)現(xiàn)代碼,在代碼中需要我們引入css與js文件,大家可以參考下文的代碼2016-09-09Egg Vue SSR 服務(wù)端渲染數(shù)據(jù)請(qǐng)求與asyncData
這篇文章主要介紹了Egg Vue SSR 服務(wù)端渲染數(shù)據(jù)請(qǐng)求與asyncData,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11el-input 標(biāo)簽中密碼的顯示和隱藏功能的實(shí)例代碼
本文通過(guò)實(shí)例代碼給大家介紹了el-input 標(biāo)簽中密碼的顯示和隱藏 ,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07js實(shí)現(xiàn)購(gòu)物車(chē)加減以及價(jià)格計(jì)算功能
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)購(gòu)物車(chē)加減以及價(jià)格計(jì)算功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08科訊商業(yè)版中用到的ajax空間與分頁(yè)函數(shù)
科訊商業(yè)版中用到的ajax空間與分頁(yè)函數(shù)...2007-09-09微信小程序使用template標(biāo)簽實(shí)現(xiàn)五星評(píng)分功能
這篇文章主要為大家詳細(xì)介紹了微信小程序使用template標(biāo)簽實(shí)現(xiàn)五星評(píng)分功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11微信小程序 動(dòng)態(tài)修改頁(yè)面數(shù)據(jù)及參數(shù)傳遞過(guò)程詳解
這篇文章主要介紹了微信小程序 動(dòng)態(tài)修改頁(yè)面數(shù)據(jù)及參數(shù)傳遞過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09JavaScript實(shí)現(xiàn)通過(guò)鍵盤(pán)彈鋼琴的效果實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于JavaScript實(shí)現(xiàn)通過(guò)鍵盤(pán)彈鋼琴效果的相關(guān)資料,通過(guò)JS代碼實(shí)現(xiàn)了鋼琴鍵盤(pán)的交互效果,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03