JavaScript中閉包的詳解
閉包是什么
在 JavaScript 中,閉包是一個(gè)讓人很難弄懂的概念。ECMAScript 中給閉包的定義是:閉包,指的是詞法表示包括不被計(jì)算的變量的函數(shù),也就是說,函數(shù)可以使用函數(shù)之外定義的變量。
是不是看完這個(gè)定義感覺更加懵逼了?別急,我們來分析一下。
- 閉包是一個(gè)函數(shù)
- 閉包可以使用在它外面定義的變量
- 閉包存在定義該變量的作用域中
好像有點(diǎn)清晰了,但是使用在它外面定義的變量是什么意思,我們先來看看變量作用域。
變量作用域
變量可分為全局變量和局部變量。全局變量的作用域就是全局性的,在 js 的任何地方都可以使用全局變量。在函數(shù)中使用 var 關(guān)鍵字聲明變量,這時(shí)的變量即是局部變量,它的作用域只在聲明該變量的函數(shù)內(nèi),在函數(shù)外面是訪問不到該變量的。
var func = function(){ var a = 'linxin'; console.log(a); // linxin } func(); console.log(a); // Uncaught ReferenceError: a is not defined
作用域相對比較簡單,我們不多講,來看看跟閉包關(guān)系比較大的變量生存周期。
變量生存周期
全局變量,生命周期是永久的。局部變量,當(dāng)定義該變量的函數(shù)調(diào)用結(jié)束時(shí),該變量就會被垃圾回收機(jī)制回收而銷毀。再次調(diào)用該函數(shù)時(shí)又會重新定義了一個(gè)新變量。
var func = function(){ var a = 'linxin'; console.log(a); } func();
a 為局部變量,在 func 調(diào)用完之后,a 就會被銷毀了。
var func = function(){ var a = 'linxin'; var func1 = function(){ a += ' a'; console.log(a); } return func1; } var func2 = func(); func2(); // linxin a func2(); // linxin a a func2(); // linxin a a a
可以看出,在第一次調(diào)用完 func2 之后,func 中的變量 a 變成 'linxin a',而沒有被銷毀。因?yàn)榇藭r(shí) func1 形成了一個(gè)閉包,導(dǎo)致了 a 的生命周期延續(xù)了。
這下子閉包就比較明朗了。
- 閉包是一個(gè)函數(shù),比如上面的 func1 函數(shù)
- 閉包使用其他函數(shù)定義的變量,使其不被銷毀。比如上面 func1 調(diào)用了變量 a
- 閉包存在定義該變量的作用域中,變量 a 存在 func 的作用域中,那么 func1 也必然存在這個(gè)作用域中。
現(xiàn)在可以說,滿足這三個(gè)條件的就是閉包了。
下面我們通過一個(gè)簡單而又經(jīng)典的例子來進(jìn)一步熟悉閉包。
for (var i = 0; i < 4; i++) { setTimeout(function () { console.log(i) }, 0) }
我們可能會簡單的以為控制臺會打印出 0 1 2 3,可事實(shí)卻打印出了 4 4 4 4,這又是為什么呢?我們發(fā)現(xiàn),setTimeout 函數(shù)時(shí)異步的,等到函數(shù)執(zhí)行時(shí),for循環(huán)已經(jīng)結(jié)束了,此時(shí)的 i 的值為 4,所以 function() { console.log(i) } 去找變量 i,只能拿到 4。
我們想起上一個(gè)例子中,閉包使 a 變量的值被保存起來了,那么這里我們也可以用閉包把 0 1 2 3 保存起來。
for (var i = 0; i < 4; i++) { (function (i) { setTimeout(function () { console.log(i) }, 0) })(i) }
當(dāng) i=0 時(shí),把 0 作為參數(shù)傳進(jìn)匿名函數(shù)中,此時(shí) function(i){} 此匿名函數(shù)中的 i 的值為 0,等到 setTimeout 執(zhí)行時(shí)順著外層去找 i,這時(shí)就能拿到 0。如此循環(huán),就能拿到想要的 0 1 2 3。
內(nèi)存管理
在閉包中調(diào)用局部變量,會導(dǎo)致這個(gè)局部變量無法及時(shí)被銷毀,相當(dāng)于全局變量一樣會一直占用著內(nèi)存。如果需要回收這些變量占用的內(nèi)存,可以手動將變量設(shè)置為null。
然而在使用閉包的過程中,比較容易形成 JavaScript 對象和 DOM 對象的循環(huán)引用,就有可能造成內(nèi)存泄露。這是因?yàn)闉g覽器的垃圾回收機(jī)制中,如果兩個(gè)對象之間形成了循環(huán)引用,那么它們都無法被回收。
function func() { var test = document.getElementById('test'); test.onclick = function () { console.log('hello world'); } }
在上面例子中,func 函數(shù)中用匿名函數(shù)創(chuàng)建了一個(gè)閉包。變量 test 是 JavaScript 對象,引用了 id 為 test 的 DOM 對象,DOM 對象的 onclick 屬性又引用了閉包,而閉包又可以調(diào)用 test ,因而形成了循環(huán)引用,導(dǎo)致兩個(gè)對象都無法被回收。要解決這個(gè)問題,只需要把循環(huán)引用中的變量設(shè)為 null 即可。
function func() { var test = document.getElementById('test'); test.onclick = function () { console.log('hello world'); } test = null; }
如果在 func 函數(shù)中不使用匿名函數(shù)創(chuàng)建閉包,而是通過引用一個(gè)外部函數(shù),也不會出現(xiàn)循環(huán)引用的問題。
function func() { var test = document.getElementById('test'); test.onclick = funcTest; } function funcTest(){ console.log('hello world'); }
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
js實(shí)現(xiàn)雙擊單元格變成文本輸入框效果代碼
單擊單元格,即可將其變?yōu)槲谋究?,方便編輯測試2008-04-04微信小程序與webview交互實(shí)現(xiàn)支付功能
這篇文章主要介紹了微信小程序與webview交互實(shí)現(xiàn)支付功能,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用小程序具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06使用getBoundingClientRect方法實(shí)現(xiàn)簡潔的sticky組件的方法
本文介紹這種組件的實(shí)現(xiàn)思路,并提供一個(gè)同時(shí)支持將sticky元素固定在頂部或底部的具體實(shí)現(xiàn),由于這種組件在網(wǎng)站中非常常見,所以有必要掌握它的實(shí)現(xiàn)方式,以便在有需要的時(shí)候基于它的思路寫出功能更多的組件出來2016-03-03js實(shí)現(xiàn)網(wǎng)頁定位導(dǎo)航功能
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)網(wǎng)頁定位導(dǎo)航功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03javascript網(wǎng)頁關(guān)鍵字高亮代碼
非常不錯(cuò)的關(guān)鍵字高亮代碼,用js實(shí)現(xiàn),這個(gè)方法不錯(cuò)2008-07-07基于jquery的高性能td和input切換并可修改內(nèi)容實(shí)現(xiàn)代碼
在實(shí)際工作中,我們會碰到這樣一個(gè)情況。在頁面中顯示著100個(gè)數(shù)據(jù),同時(shí)用戶還希望他可以更改其中的數(shù)據(jù),普通的方式可能如下2011-01-01