前端學(xué)習(xí)14 閉包
1.閉包
閉包是 JavaScript 中的一個(gè)核心概念,它允許函數(shù)訪問其定義時(shí)的詞法作用域中的變量。這種特性使得閉包在數(shù)據(jù)封裝、回調(diào)函數(shù)、模塊模式等場景中有著廣泛的應(yīng)用。
1.1 閉包的定義:
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域的變量的函數(shù)。在JavaScript中,閉包可以通過函數(shù)嵌套和變量應(yīng)用實(shí)現(xiàn)。
function a() {
let x = '我在outer函數(shù)里!';
function b() {
console.log(x);
}
return b;
}
const c = a();
c(); // 輸出: 我在outer函數(shù)里!
在這個(gè)例子中,b函數(shù)引用了x變量,因此JavaScript引擎會(huì)保留a函數(shù)的作用域鏈,以便b函數(shù)可以訪問x變量。
1.2閉包的實(shí)現(xiàn)
閉包是指一個(gè)函數(shù)可以訪問它定義時(shí)所在的詞法作用域以及全局作用域中的變量。當(dāng)內(nèi)部函數(shù)引用外部函數(shù)的變量時(shí),外部函數(shù)的作用域鏈將被保留在內(nèi)存中,以便內(nèi)部函數(shù)可以訪問這些變量。
function a() {
var aa = 333;
function b() {
console.log(aa); // 輸出:333
}
return b;
}
var demo = a();
demo(); // 輸出:333
在這個(gè)例子中,b函數(shù)引用了a函數(shù)中的變量aa,因此即使a函數(shù)執(zhí)行完畢后,aa變量仍然可以通過b函數(shù)被訪問,形成了閉包。
2.閉包的用途
2.1 封裝私有變量
閉包可以用于封裝私有變量,以防止其被外部訪問和修改。這可以減少全局變量的數(shù)量,降低全局變量被誤用或意外修改的風(fēng)險(xiǎn)。
function addFn() {
let count = 1;
function increment() {
count++;
console.log(count);
}
return increment;
}
const counter = addFn();
counter(); // 輸出:2
counter(); // 輸出:3
在這個(gè)例子中,increment函數(shù)通過閉包訪問并修改了count變量,而count變量不會(huì)被外部直接訪問。
2.2 做緩存
閉包可以用于緩存計(jì)算結(jié)果,減少重復(fù)計(jì)算。
function fn1() {
var type = 'JavaScript';
let tt1 = 1;
const tt2 = 2;
var innerBar = {
getType: function() {
console.log(tt1);
return type;
},
setType: function(newType) {
type = newType;
}
};
return innerBar;
}
var bar = fn1();
console.log(bar.getType()); // 輸出:1 JavaScript
bar.setType('Python');
console.log(bar.getType()); // 輸出:1 Python
在這個(gè)例子中,getType和setType方法通過閉包訪問并修改了type變量。
2.3 模塊化編程
閉包可以用于實(shí)現(xiàn)模塊化編程,封裝模塊的私有變量和方法。
const moduleFn = (function() {
let privateVar = '我是私有變量!';
function privateMethod() {
console.log(privateVar);
}
return {
publicMethod: function() {
privateMethod();
}
};
})();
moduleFn.publicMethod(); // 輸出:我是私有的!
在這個(gè)例子中,privateVar和privateMethod被封裝在模塊內(nèi)部,只能通過publicMethod訪問。
2.4 防抖
防抖的本質(zhì)需求是:在多次觸發(fā)事件時(shí),能夠記住上一次定時(shí)器(timer),并且在新的觸發(fā)時(shí)清除掉舊的定時(shí)器。
function antishake(fn, wait) {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn();
}, wait);
};
}
let an = antishake(function() {
console.log('555');
}, 2000);
document.querySelector('div').onmouseenter = () => {
an();
};
當(dāng)鼠標(biāo)進(jìn)入div時(shí),執(zhí)行an(),也就是調(diào)用了內(nèi)部函數(shù),clearTiimeout(timer),清除之前的setTimeout,然后重新設(shè)置一個(gè)定時(shí)器。
如果鼠標(biāo)在2秒內(nèi)反復(fù)移動(dòng)到div上,每次都會(huì)清除舊的setTimeout。
閉包讓 timer 變量在多次事件觸發(fā)過程中保持持久存在,能被正確讀取和修改,從而實(shí)現(xiàn)防抖。
2.5 節(jié)流
節(jié)流的核心思想為:一段時(shí)間內(nèi),只允許執(zhí)行一次函數(shù) fn。如果在 wait 時(shí)間內(nèi)多次觸發(fā)事件,都不會(huì)觸發(fā)函數(shù)。
function throttle(fn, wait) {
let timer = null;
return function() {
if (timer) return;
timer = setTimeout(() => {
fn();
timer = null;
}, wait);
};
}
let throttle1 = throttle(() => {
console.log('我上車了');
}, 2000);
document.querySelector('div').onclick = () => {
throttle1();
};
節(jié)流需要記錄“上一次觸發(fā)時(shí)間”或“是否還在等待中”,否則就無法判斷下一次是否可以執(zhí)行。 首次點(diǎn)擊時(shí) timer 是 null,允許執(zhí)行函數(shù);
設(shè)置 timer,2秒后執(zhí)行函數(shù)并將 timer 設(shè)為 null;
若用戶在這 2 秒內(nèi)重復(fù)點(diǎn)擊,timer 不為 null,直接 return 不執(zhí)行。
所以閉包保存并共享了 timer 變量狀態(tài),保證了“時(shí)間窗”內(nèi)只執(zhí)行一次。
3.閉包的缺點(diǎn)
閉包不會(huì)導(dǎo)致內(nèi)存泄漏。
內(nèi)存泄露是指你用不到(訪問不到)的變量,依然占居著內(nèi)存空間,不能被再次利用起來。
閉包里面的變量明明就是我們需要的變量(lives),憑什么說是內(nèi)存泄露?。
因?yàn)?IE。IE 有 bug,IE 在我們使用完閉包之后,依然回收不了閉包里面引用的變量。
這是 IE 的問題,不是閉包的問題。
4.總結(jié)
閉包是JavaScript中一個(gè)強(qiáng)大的特性,它允許函數(shù)訪問其創(chuàng)建時(shí)的詞法作用域。通過閉包,可以實(shí)現(xiàn)數(shù)據(jù)封裝、事件處理、延遲執(zhí)行等多種高級功能。理解閉包的概念和工作原理對于深入掌握J(rèn)avaScript至關(guān)重要。
#前端學(xué)習(xí)#