JavaScript深入理解節(jié)流與防抖
一、js防抖和節(jié)流
在進(jìn)行窗口的resize、scroll、輸出框內(nèi)容校驗(yàn)等操縱的時(shí)候,如果事件處理函數(shù)調(diào)用的頻率無限制,會(huì)加重瀏覽器的負(fù)擔(dān),導(dǎo)致用戶體驗(yàn)非常之差。那么為了前端性能的優(yōu)化也為了用戶更好的體驗(yàn),就可以采用防抖(debounce)和節(jié)流(throttle)的方式來到達(dá)這種效果,減少調(diào)用的頻率。
二、為什么滾動(dòng)scroll、窗口resize等事件需要優(yōu)化
滾動(dòng)事件的應(yīng)用很頻繁:圖片懶加載、下滑自動(dòng)加載數(shù)據(jù)、側(cè)邊浮動(dòng)導(dǎo)航欄等。
在綁定scroll、resize事件時(shí),但它發(fā)生的時(shí)候,它被觸發(fā)的頻率非常高,間隔很近。在日常開發(fā)的頁面中,事件中涉及到的大量的位置計(jì)算、DOM操作、元素重繪等等這些都無法在下一個(gè)scroll事件出發(fā)前完成的情況下,瀏覽器會(huì)卡幀。
三、滾動(dòng)和頁面渲染前端性能優(yōu)化的關(guān)系
為什么滾動(dòng)需要優(yōu)化?前面提到了事件中涉及到大量的位置計(jì)算、DOM操作、元素重繪等等,這些又與頁面的渲染有關(guān)系,頁面渲染又與前端的性能優(yōu)化有關(guān)系?套娃一樣,一層套著一層,每一層都聯(lián)系緊密,每一層都如此平凡且重要。
review一下前端的渲染和優(yōu)化
web頁面展示經(jīng)歷的步驟:js—style—layout—paint—composite,簡單回顧一下,在這里不做詳細(xì)的介紹哦!
網(wǎng)頁生成的時(shí)候,至少會(huì)渲染(Layout+Paint)一次。用戶訪問的過程中,還會(huì)不斷重新的重排(reflow)和重繪(repaint),用戶scroll行為和resize行為會(huì)導(dǎo)致頁面不斷的進(jìn)行重新渲染,而且間隔頻繁,容易造成瀏覽器卡幀。
四、防抖Debounce
1 防抖Debounce情景
①有些場景事件觸發(fā)的頻率過高(mousemove onkeydown onkeyup onscroll)
②回調(diào)函數(shù)執(zhí)行的頻率過高也會(huì)有卡頓現(xiàn)象。 可以一段時(shí)間過后進(jìn)行觸發(fā)去除無用操作
2 防抖原理
一定在事件觸發(fā) n 秒后才執(zhí)行,如果在一個(gè)事件觸發(fā)的 n 秒內(nèi)又觸發(fā)了這個(gè)事件,以新的事件的時(shí)間為準(zhǔn),n 秒后才執(zhí)行,等觸發(fā)事件 n 秒內(nèi)不再觸發(fā)事件才執(zhí)行。
官方解釋:當(dāng)持續(xù)觸發(fā)事件時(shí),一定時(shí)間段內(nèi)沒有再觸發(fā)事件,事件處理函數(shù)才會(huì)執(zhí)行一次,如果設(shè)定的時(shí)間到來之前,又一次觸發(fā)了事件,就重新開始延時(shí)。
3 防抖函數(shù)簡單實(shí)現(xiàn)
//簡單的防抖函數(shù)
function debounce(func, wait, immediate) {
//定時(shí)器變量
var timeout;
return function () {
//每次觸發(fā)scrolle,先清除定時(shí)器
clearTimeout(timeout);
//指定多少秒后觸發(fā)事件操作handler
timeout = setTimeout(func, wait);
};
};
//綁定在scrolle事件上的handler
function handlerFunc() {
console.log('Success');
}
//沒采用防抖動(dòng)
window.addEventListener('scroll', handlerFunc);
//采用防抖動(dòng)
window.addEventListener('scrolle', debounce(handlerFunc, 1000));
4 防抖函數(shù)的演化過程
①this event綁定問題
//以閉包的形式返回一個(gè)函數(shù),內(nèi)部解決了this指向的問題,event對(duì)象傳遞的問題
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
};
};
②立即觸發(fā)問題
//首次觸發(fā)執(zhí)行,再次觸發(fā)以后開始執(zhí)行防抖函數(shù)
//使用的時(shí)候不用重復(fù)執(zhí)行這個(gè)函數(shù),本身返回的函數(shù)才具有防抖功能
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if(timeout) clearTimeout(timeout);
// 是否在某一批事件中首次執(zhí)行
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args)
immediate = true;
}, wait);
if (callNow) {
func.apply(context, args)
}
immediate = false;
} else {
timeout = setTimeout(function() {
func.apply(context, args);
immediate = true;
}, wait);
}
}
}
③返回值問題
function debounce(func, wait, immediate) {
var timeout, result;
return function () {
var context = this, args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
result = func.apply(context, args)
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function() {
result = func.apply(context, args)
}, wait);
}
return result;
}
}
④取消防抖,添加cancel方法
function debounce(func, wait, immediate) {
var timeout, result;
function debounced () {
var context = this, args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
result = func.apply(context, args)
}, wait);
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function() {
result = func.apply(context, args)
}, wait);
}
return result;
}
debounced.cancel = function(){
cleatTimeout(timeout);
timeout = null;
}
return debounced;
}
五、節(jié)流Throttle
1 節(jié)流Throttle情景
①圖片懶加載
②ajax數(shù)據(jù)請求加載
2 節(jié)流原理
如果持續(xù)觸發(fā)事件,每隔一段時(shí)間只執(zhí)行一次函數(shù)。
官方解釋:當(dāng)持續(xù)觸發(fā)事件時(shí),保證一定時(shí)間段內(nèi)只調(diào)用一次事件處理函數(shù)。
3 節(jié)流實(shí)現(xiàn)—時(shí)間戳和定時(shí)器
時(shí)間戳
var throttle = function (func, delay) {
var prev = Date.now()
return function () {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
定時(shí)器
var throttle = function (func, delay) {
var timer = null;
return function () {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function () {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000))
4 節(jié)流函數(shù)的演化過程
①時(shí)間戳觸發(fā)
//在開始觸發(fā)時(shí),會(huì)立即執(zhí)行一次; 如果最后一次沒有超過 wait 值,則不觸發(fā)
function throttle(func, wait) {
var context, args;
var previous = 0; // 初始的時(shí)間點(diǎn),也是關(guān)鍵的時(shí)間點(diǎn)
return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
②定時(shí)器觸發(fā)
//首次不會(huì)立即執(zhí)行,最后一次會(huì)執(zhí)行,和用時(shí)間戳的寫法互補(bǔ)。
function throttle(func, wait){
var context, args, timeout;
return function() {
context = this;
args = arguments;
if(!timeout) {
timeout = setTimeout(function(){
func.apply(context, args);
timeout = null;
}, wait);
}
}
}
③結(jié)合時(shí)間戳和定時(shí)器
function throttle(func, wait) {
var timer = null;
var startTime = Date.now();
return function(){
var curTime = Date.now();
var remaining = wait-(curTime-startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if(remaining<=0){
func.apply(context, args);
startTime = Date.now();
}else{
timer = setTimeout(fun, remaining); // 如果小于wait 保證在差值時(shí)間后執(zhí)行
}
}
}
六、節(jié)流防抖總結(jié)
防抖:原理是維護(hù)一個(gè)定時(shí)器,將很多個(gè)相同的操作合并成一個(gè)。規(guī)定在delay后觸發(fā)函數(shù),如果在此之前觸發(fā)函數(shù),則取消之前的計(jì)時(shí)重新計(jì)時(shí),只有最后一次操作能被觸發(fā)。
節(jié)流:原理是判斷是否達(dá)到一定的時(shí)間來觸發(fā)事件。某個(gè)時(shí)間段內(nèi)只能觸發(fā)一次函數(shù)。
區(qū)別:防抖只會(huì)在最后一次事件后執(zhí)行觸發(fā)函數(shù),節(jié)流不管事件多么的頻繁,都會(huì)保證在規(guī)定時(shí)間段內(nèi)觸發(fā)事件函數(shù)。
到此這篇關(guān)于JavaScript深入理解節(jié)流與防抖的文章就介紹到這了,更多相關(guān)JS 節(jié)流與防抖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript函數(shù)防抖動(dòng)debounce
- 通過實(shí)例講解JS如何防抖動(dòng)
- JavaScript運(yùn)動(dòng)框架 解決防抖動(dòng)問題、懸浮對(duì)聯(lián)(二)
- JavaScript中防抖和節(jié)流的區(qū)別及適用場景
- JavaScript中防抖和節(jié)流的實(shí)戰(zhàn)應(yīng)用記錄
- JavaScript防抖與節(jié)流的實(shí)現(xiàn)與注意事項(xiàng)
- JavaScript的防抖和節(jié)流一起來了解下
- JavaScript中函數(shù)的防抖與節(jié)流詳解
- javascript的防抖和節(jié)流你了解嗎
- 淺談JavaScript節(jié)流與防抖
- 關(guān)于JavaScript防抖與節(jié)流的區(qū)別與實(shí)現(xiàn)
- JavaScript防抖與節(jié)流詳解
- JavaScript 防抖和節(jié)流詳解
- JavaScript防抖動(dòng)與節(jié)流處理
相關(guān)文章
JavaScript中創(chuàng)建字典對(duì)象(dictionary)實(shí)例
這篇文章主要介紹了JavaScript中創(chuàng)建字典對(duì)象(dictionary)實(shí)例,本文直接給出了實(shí)現(xiàn)的源碼,并給出了使用示例,需要的朋友可以參考下2015-03-03
jquery中click等事件綁定及移除的幾種方法小結(jié)
這篇文章主要介紹了jquery中綁定事件與解綁事件是常用到的寫法,這里總結(jié)了幾種常見的綁定事件與解綁事件的方法,需要的朋友可以參考下2023-05-05
CryptoJS中AES實(shí)現(xiàn)前后端通用加解密技術(shù)
這篇文章主要為大家詳細(xì)介紹了CryptoJS中AES實(shí)現(xiàn)前后端通用加解密技術(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
在IE中調(diào)用javascript打開Excel的代碼(downmoon原作)
在IE中調(diào)用javascript打開Excel的代碼(downmoon原作)...2007-04-04
Taro小程序自定義頂部導(dǎo)航欄功能的實(shí)現(xiàn)
這篇文章主要介紹了Taro小程序自定義頂部導(dǎo)航欄功能的實(shí)現(xiàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

