概念
閉包是指有權(quán)訪問另外一個函數(shù)作用域中的變量的函數(shù)
// 1、箭頭函數(shù)體的閉包( i=0 是默認(rèn)參數(shù))
var Add = (i = 0) => {
return (() => (++i))
};
var v = Add();
v(); //1
v(); //2
閉包的優(yōu)點(diǎn)
可以重復(fù)使用變量墩剖,并且不會造成變量污染
- 全局變量可以重復(fù)使用猴凹,但是容易造成變量污染。局部變量僅在局部作用域內(nèi)有效岭皂,不可以重復(fù)使用郊霎,不會造成變量污染。閉包結(jié)合了全局變量和局部變量的優(yōu)點(diǎn)爷绘。
可以用來定義私有屬性和私有方法书劝。
閉包的缺點(diǎn)
比普通函數(shù)更占用內(nèi)存,會導(dǎo)致網(wǎng)頁性能變差土至,在IE下容易造成內(nèi)存泄露购对。
什么是內(nèi)存泄漏
首先,需要了解瀏覽器自身的內(nèi)存回收機(jī)制陶因。
每個瀏覽器會有自己的一套回收機(jī)制骡苞,當(dāng)分配出去的內(nèi)存不使用的時候便會回收;內(nèi)存泄露的根本原因就是你的代碼中分配了一些‘頑固的’內(nèi)存楷扬,瀏覽器無法進(jìn)行回收解幽,如果這些’頑固的’內(nèi)存還在一直不停地分配就會導(dǎo)致后面所用內(nèi)存不足,造成泄露烘苹。
閉包造成內(nèi)存泄漏
因為閉包就是能夠訪問外部函數(shù)變量的一個函數(shù)躲株,而函數(shù)是必須保存在內(nèi)存中的對象,所以位于函數(shù)執(zhí)行上下文中的所有變量也需要保存在內(nèi)存中镣衡,這樣就不會被回收徘溢,如果一旦循環(huán)引用或創(chuàng)建閉包吞琐,就會占據(jù)大量內(nèi)存,可能會引起內(nèi)存泄漏
內(nèi)存泄漏的解決方案
造成內(nèi)存泄露的原因:
- 意外的全局變量(在函數(shù)內(nèi)部沒有使用var進(jìn)行聲明的變量)
- console.log
- 閉包
- 對象的循環(huán)引用
- 未清除的計時器
- DOM泄露(獲取到DOM節(jié)點(diǎn)之后然爆,將DOM節(jié)點(diǎn)刪除,但是沒有手動釋放變量黍图,拿對應(yīng)的DOM節(jié)點(diǎn)在變量中還可以訪問到曾雕,就會造成泄露)
如何避免閉包引起的內(nèi)存泄漏:
- 在退出函數(shù)之前,將不使用的局部變量全部刪除助被,可以使變量賦值為null
//這段代碼會導(dǎo)致內(nèi)存泄露
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}
//解決方法為
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循環(huán)引用
el.onclick = function(){
alert(id);
}
el = null; // 將閉包引用的外部函數(shù)中活動對象清除
}
- 避免變量的循環(huán)賦值和引用(代碼如上)
- 由于jQuery考慮到了內(nèi)存泄漏的潛在危害剖张,所以它會手動釋放自己指定的所有事件處理程序。只要堅持使用jQuery的事件綁定方法揩环,就可以一定程度上避免這種特定的常見原因?qū)е碌膬?nèi)存泄漏搔弄。
//這段代碼會導(dǎo)致內(nèi)存泄露
$(document).ready(function() {
var button = document.getElementById('button-1');
button.onclick = function() {
console.log('hello');
return false;
};
});
//當(dāng)指定單擊事件處理程序時,就創(chuàng)建了一個在其封閉的環(huán)境中包含button變量的閉包丰滑。而且顾犹,現(xiàn)在的button也包含一個指向閉包(onclick屬性自身)的引用。這樣褒墨,就導(dǎo)致了在IE中即使離開當(dāng)前頁面也不會釋放這個循環(huán)炫刷。
//用jQuery化解引用循環(huán)
$(document).ready(function() {
var $button = $('#button-1');
$button.click(function(event) {
event.preventDefault();
console.log('hello');
});
});
閉包的使用場景
封裝功能時(需要使用私有的屬性和方法),函數(shù)防抖郁妈、函數(shù)節(jié)流浑玛、函數(shù)柯里化、給元素偽數(shù)組添加事件需要使用元素的索引值噩咪。
閉包實(shí)例--函數(shù)防抖
控制函數(shù)執(zhí)行時機(jī)
/**
* @function debounce 函數(shù)防抖
* @param {Function} fn 需要防抖的函數(shù)
* @param {Number} interval 間隔時間
* @return {Function} 經(jīng)過防抖處理的函數(shù)
* */
function debounce(fn, interval) {
let timer = null; // 定時器
return function() {
// 清除上一次的定時器
clearTimeout(timer);
// 拿到當(dāng)前的函數(shù)作用域
let _this = this;
// 拿到當(dāng)前函數(shù)的參數(shù)數(shù)組
let args = Array.prototype.slice.call(arguments, 0);
// 開啟倒計時定時器
timer = setTimeout(function() {
// 通過apply傳遞當(dāng)前函數(shù)this顾彰,以及參數(shù)
fn.apply(_this, args);
// 默認(rèn)300ms執(zhí)行
}, interval || 300)
}
}
概念:
就是指觸發(fā)事件后在 n 秒內(nèi)函數(shù)只能執(zhí)行一次,如果在 n 秒內(nèi)又觸發(fā)了事件胃碾,則會重新計算函數(shù)執(zhí)行時間涨享。
通俗一點(diǎn):在一段固定的時間內(nèi),只能觸發(fā)一次函數(shù)书在,在多次觸發(fā)事件時灰伟,只執(zhí)行最后一次。
使用時機(jī):
- 搜索功能儒旬,在用戶輸入結(jié)束以后才開始發(fā)送搜索請求栏账,可以使用函數(shù)防抖來實(shí)現(xiàn);
閉包實(shí)例--函數(shù)節(jié)流
控制函數(shù)執(zhí)行頻率
/**
* @function throttle 函數(shù)節(jié)流
* @param {Function} fn 需要節(jié)流的函數(shù)
* @param {Number} interval 間隔時間
* @return {Function} 經(jīng)過節(jié)流處理的函數(shù)
* */
function throttle(fn, interval) {
let timer = null; // 定時器
let firstTime = true; // 判斷是否是第一次執(zhí)行
// 利用閉包
return function() {
// 拿到函數(shù)的參數(shù)數(shù)組
let args = Array.prototype.slice.call(arguments, 0);
// 拿到當(dāng)前的函數(shù)作用域
let _this = this;
// 如果是第一次執(zhí)行的話栈源,需要立即執(zhí)行該函數(shù)
if(firstTime) {
// 通過apply挡爵,綁定當(dāng)前函數(shù)的作用域以及傳遞參數(shù)
fn.apply(_this, args);
// 修改標(biāo)識為null,釋放內(nèi)存
firstTime = null;
}
// 如果當(dāng)前有正在等待執(zhí)行的函數(shù)則直接返回
if(timer) return;
// 開啟一個倒計時定時器
timer = setTimeout(function() {
// 通過apply甚垦,綁定當(dāng)前函數(shù)的作用域以及傳遞參數(shù)
fn.apply(_this, args);
// 清除之前的定時器
timer = null;
// 默認(rèn)300ms執(zhí)行一次
}, interval || 300)
}
}
概念
就是限制一個函數(shù)在一定時間內(nèi)只能執(zhí)行一次茶鹃。
使用時機(jī)
- 改變?yōu)g覽器窗口尺寸涣雕,可以使用函數(shù)節(jié)流,避免函數(shù)不斷執(zhí)行闭翩;
- 滾動條scroll事件挣郭,通過函數(shù)節(jié)流,避免函數(shù)不斷執(zhí)行疗韵。
函數(shù)節(jié)流與函數(shù)防抖的區(qū)別:
我們以一個案例來講一下它們之間的區(qū)別:
設(shè)定一個間隔時間為一秒兑障,在一分鐘內(nèi),不斷的移動鼠標(biāo)蕉汪,讓它觸發(fā)一個函數(shù)流译,打印一些內(nèi)容。
- 函數(shù)防抖:會打印1次者疤,在鼠標(biāo)停止移動的一秒后打印福澡。
- 函數(shù)節(jié)流:會打印60次,因為在一分鐘內(nèi)有60秒驹马,每秒會觸發(fā)一次革砸。
- 總結(jié):節(jié)流是為了限制函數(shù)的執(zhí)行次數(shù),而防抖是為了限制函數(shù)的執(zhí)行時機(jī)窥翩。
函數(shù)節(jié)流與函數(shù)防抖的使用
此處使用一個對象的方法业岁,主要為了測試this指向綁定的問題,調(diào)用的時候傳遞參數(shù)問題等寇蚊。
function log(a,b) {
console.log(a,b);
console.log(this);
}
const throttleLog = throttle(log, 1000);
const debounceLog = debounce(log, 1000);
let a = {
b: throttleLog,
c: debounceLog
};
document.body.onmousemove = function() {
a.b('throttle', 'log');
a.c('debounce', 'log');
};
閉包實(shí)例--函數(shù)柯里化
閉包實(shí)例--給元素偽數(shù)組添加事件
// DOM操作
let li = document.querySelectorAll('li');
for(var i = 0; i < li.length; i++) {
(function(i){
li[i].onclick = function() {
alert(i);
}
})(i)
}
閉包實(shí)例--不使用循環(huán)返回數(shù)組
function getArr() {
let num = 10;
let arr = [];
return (function(){
arr.unshift(num);
num--;
if(num > 0) {
arguments.callee();
}
return arr;
})()
}
console.log(getArr()); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]