簡(jiǎn)單點(diǎn)說:「函數(shù)」和「函數(shù)內(nèi)部能訪問到的變量」(也叫環(huán)境)的總和朋截,就是一個(gè)閉包辨泳。
閉包有3個(gè)特性:
①函數(shù)嵌套函數(shù)
②函數(shù)內(nèi)部可以引用函數(shù)外部的參數(shù)和變量
③參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收
閉包的作用
閉包常常用來「間接訪問一個(gè)變量」峦椰。換句話說盾剩,「隱藏一個(gè)變量」出刷。
假設(shè)我們?cè)谧鲆粋€(gè)游戲瘾腰,在寫其中關(guān)于「還剩幾條命」的代碼板鬓。
如果不用閉包悲敷,你可以直接用一個(gè)全局變量:
window.lives = 30 // 還有三十條命
這樣看起來很不妥。萬一不小心把這個(gè)值改成 -1 了怎么辦俭令。所以我們不能讓別人「直接訪問」這個(gè)變量后德。怎么辦呢?
用局部變量抄腔。
但是用局部變量別人又訪問不到瓢湃,怎么辦呢理张?
暴露一個(gè)訪問器(函數(shù)),讓別人可以「間接訪問」绵患。
代碼如下:
```
!function(){
? var lives = 50
? window.獎(jiǎng)勵(lì)一條命 = function(){
? ? lives += 1
? }
? window.死一條命 = function(){
? ? lives -= 1
? }
}()
```
再來看一個(gè)經(jīng)典例子-定時(shí)器與閉包
寫一個(gè)for循環(huán)雾叭,讓它按順序打印出當(dāng)前循環(huán)次數(shù)
再來看一個(gè)經(jīng)典例子-定時(shí)器與閉包
寫一個(gè)for循環(huán),讓它按順序打印出當(dāng)前循環(huán)次數(shù)
按照預(yù)期它應(yīng)該依次輸出1 2 3 4 5落蝙,而結(jié)果它輸出了五次5织狐,這是為什么呢?原來由于js是單線程的筏勒,所以在執(zhí)行for循環(huán)的時(shí)候定時(shí)器setTimeout被安排到任務(wù)隊(duì)列中排隊(duì)等待執(zhí)行移迫,而在等待過程中for循環(huán)就已經(jīng)在執(zhí)行,等到setTimeout可以執(zhí)行的時(shí)候管行,for循環(huán)已經(jīng)結(jié)束厨埋,i的值也已經(jīng)編程5,所以打印出來五個(gè)5捐顷,那么我們?yōu)榱藢?shí)現(xiàn)預(yù)期結(jié)果應(yīng)該怎么改這段代碼呢揽咕?(ps:如果把for循環(huán)里面的var變成let,也能實(shí)現(xiàn)預(yù)期結(jié)果)
引入閉包來保存變量i套菜,將setTimeout放入立即執(zhí)行函數(shù)中,將for循環(huán)中的循環(huán)值i作為參數(shù)傳遞设易,100毫秒后同時(shí)打印出1 2 3 4 5
那如果我們想實(shí)現(xiàn)每隔100毫秒分別依次輸出數(shù)字逗柴,又該怎么改呢?
在這段代碼中,相當(dāng)于同時(shí)啟動(dòng)3個(gè)定時(shí)器顿肺,i*100是為4個(gè)定時(shí)器分別設(shè)置了不同的時(shí)間戏溺,同時(shí)啟動(dòng),但是執(zhí)行時(shí)間不同屠尊,每個(gè)定時(shí)器間隔都是100毫秒旷祸,實(shí)現(xiàn)了每隔100毫秒就執(zhí)行一次打印的效果。
最后總結(jié)一下閉包的好處與壞處
好處
①保護(hù)函數(shù)內(nèi)的變量安全 讼昆,實(shí)現(xiàn)封裝托享,防止變量流入其他環(huán)境發(fā)生命名沖突
②在內(nèi)存中維持一個(gè)變量,可以做緩存(但使用多了同時(shí)也是一項(xiàng)缺點(diǎn)浸赫,消耗內(nèi)存)
③匿名自執(zhí)行函數(shù)可以減少內(nèi)存消耗
壞處
①其中一點(diǎn)上面已經(jīng)有體現(xiàn)了闰围,就是被引用的私有變量不能被銷毀,增大了內(nèi)存消耗既峡,造成內(nèi)存泄漏羡榴,解決方法是可以在使用完變量后手動(dòng)為它賦值為null;
②其次由于閉包涉及跨域訪問运敢,所以會(huì)導(dǎo)致性能損失校仑,我們可以通過把跨作用域變量存儲(chǔ)在局部變量中忠售,然后直接訪問局部變量,來減輕對(duì)執(zhí)行速度的影響