前言
早在之前我對閉包還不是很理解的時候氧骤,我把它抽象的認為是函數(shù)里面寫函數(shù)颅崩,有點滑稽几于。其實閉包的正確定義是,可以訪問另外函數(shù)作用域內(nèi)部變量的函數(shù)沿后。這樣它就可以重復(fù)使用變量沿彭,且不會造成變量污染。
一
什么是變量污染尖滚?先看一段經(jīng)典代碼喉刘,在那個沒有三大框架的年代,要給列表添加點擊事件時:
const list = document.querySelector('li');
for (var i = 0; i < list.length - 1; i++) {
list[i].onclick = function(i) {
console.log(i);
}
}
// 為了方便執(zhí)行漆弄,用setTimeout函數(shù)代替給dom添加點擊事件
for (var i = 0; i < 5; i++) {
console.log('直接打印', i);
setTimeout(() => { console.log('延遲打印', i) });
}
// 直接打印 0
// 直接打印 1
// 直接打印 2
// 直接打印 3
// 直接打印 4
// 延遲打印 5
// 延遲打印 5
// 延遲打印 5
// 延遲打印 5
// 延遲打印 5
這樣寫的問題就是睦裳,無論你點擊哪個<li>
標簽,總是打印5
撼唾,這是因為使用var
聲明變量時廉邑,會創(chuàng)建全局變量;上面的代碼類似于這樣:
var i = 0;
for (i < 5; i++) {
console.log('直接打印', i);
setTimeout(() => { console.log('延遲打印', i) });
}
其實每次循環(huán)都是在操作同一個變量倒谷,這就導(dǎo)致了變量污染蛛蒙,等到觸發(fā)點擊事件時,這個i
就已經(jīng)等于5
了渤愁。我還記得我當時是這樣處理的牵祟,使用一個立即執(zhí)行函數(shù)包裹一下,這樣就通過立即執(zhí)行函數(shù)創(chuàng)造了一個局部作用域抖格,保存住了每次循環(huán)的變量i
诺苹,每個作用域塊內(nèi)的變量不會互相污染:
for (var i = 0; i < 5; i++) {
console.log('直接打印', i);
(function(i) {
setTimeout(() => { console.log('延遲打印', i) });
})(i);
}
// 直接打印 0
// 直接打印 1
// 直接打印 2
// 直接打印 3
// 直接打印 4
// 延遲打印 0
// 延遲打印 1
// 延遲打印 2
// 延遲打印 3
// 延遲打印 4
但是現(xiàn)在有了let
,這個問題就簡單得多了他挎,因為使用let
聲明變量時自帶作用域筝尾,其實for循環(huán)表達式部分是一塊單獨的作用域,其內(nèi)部的i
是繼承了父作用域的i
值:
for (let i = 0; i < 5; i++) {
console.log('直接打印', i);
setTimeout(() => { console.log('延遲打印', i) });
}
// 直接打印 0
// 直接打印 1
// 直接打印 2
// 直接打印 3
// 直接打印 4
// 延遲打印 0
// 延遲打印 1
// 延遲打印 2
// 延遲打印 3
// 延遲打印 4
而且Vue
有了v-for
指令办桨,想給列表加點擊事件筹淫,直接在標簽上添加就可以了。
其實在我們使用立即執(zhí)行函數(shù)保存變量時,就是在使用閉包了损姜。
二
而如今饰剥,閉包應(yīng)用很經(jīng)典的例子就是去抖和節(jié)流。
1.去抖
去抖函數(shù)是應(yīng)用于短時間內(nèi)連續(xù)多次調(diào)用同一個函數(shù)場景摧阅。我們用一個延遲函數(shù)去阻止它立即執(zhí)行汰蓉,當短時間內(nèi)再次調(diào)用,我們清除上一個延遲函數(shù)棒卷,重新創(chuàng)建一個延遲函數(shù)顾孽,直到最后不再調(diào)用目標函數(shù),則延遲時間到達執(zhí)行一次目標函數(shù)比规。應(yīng)用場景就是列表篩選若厚,需求為輸入內(nèi)容改變就調(diào)用接口篩選列表,當連續(xù)輸入內(nèi)容時蜒什,我們不能一直調(diào)用接口测秸,只在用戶輸入完調(diào)用一次接口即可,這樣可以大大降低網(wǎng)絡(luò)開銷灾常。
const debounce = (fn, ms = 500) => {
let timer = null;
return (...rest) => {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn(...rest);
clearTimeout(timer);
timer=null;
}, ms);
};
};
2.節(jié)流
節(jié)流函數(shù)也是應(yīng)用于短時間內(nèi)連續(xù)多次調(diào)用同一個函數(shù)場景霎冯。常見用于頁面滾動時,不斷拉取數(shù)據(jù)钞瀑,也是不能每次觸發(fā)滾動都去請求接口沈撞,我們同樣做一個延遲函數(shù),每次觸發(fā)只要前一個延遲函數(shù)還在就不做任何操作仔戈,等到前一個延遲函數(shù)執(zhí)行完关串,我們就再創(chuàng)建一個延遲函數(shù)拧廊,達到的效果就是每隔一定間隔去拉取數(shù)據(jù)监徘,也是可以大大降低網(wǎng)絡(luò)開銷。
const throttle = (fn, ms = 500) => {
let timer = null;
return (...rest) => {
if (!timer) {
timer = setTimeout(() => {
fn(...rest);
clearTimeout(timer);
timer = null;
}, ms);
}
};
}