背景:
我们在日常开发中,经常会遇到处理系统高频回调的问题,比如:scrollView 快速滚动的回调(scrollViewDidScroll),又比如用户输入文本时textView的回调(textViewDidChange),在这些场景下,为了降低CPU负担,我们一般会使用一些方法降低高频次计算,这篇文章介绍解决高频次计算的两种方法:防抖(debounce) 和 节流(throttle)。
防抖
首先提出第一种思路,防抖:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:
如果在200ms内,事件再次触发,当前的计时取消,重新开始计时
如果在200ms内,事件没有触发,那么执行事件的处理函数
总结:指定时间内连续触发事件,只在最后一次事件触发结束后的指定时间之后,执行一次处理函数。
/**
* 函数防抖
* @param fn 函数
* @param delay 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
export function _debounce(fn,delay,immediate){
var delay = delay||200;
var timer;
return function(){
var th = this;
var args = arguments;
if (timer) {
clearTimeout(timer);
}
if(immediate){
var callNow = !timer
timer = setTimeout(()=>{
timer = null;
},delay)
if (callNow) fn.apply(th, args)
}else {
timer = setTimeout(function(){
timer = null;
fn.apply(th,args);
},delay)
}
}
}
节流
当事件第一次触发的时候,也是不立即执行函数,而是给出一个期限值比如200ms,开始计时,然后
在200ms内,再次触发的事件被全部忽略,计时结束后,执行一次函数,并且清理计时器。
200ms结束后,重新开始上述循环
总结:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
//节流
// 函数节流
// param fn 函数
// param interval 延迟执行毫秒数
// param type 1 表时间戳版,2 表定时器版
//时间戳和定时器区别:时间戳版本的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候
export function _throttle(fn,interval,type) {
if(type === 1){
let previous = 0;
}else if(type === 2){
let timeout;
}
var interval = interval || 200;
return function () {
var th = this;
var args = arguments;
if(type === 1){
let now = Date.now();
if (now - previous > interval){
fn.apply(th,args);
previous = now;
}
}else if(type === 2){
if(!timeout){
timeout = setTimeout(()=> {
timeout = null;
fn.apply(th,args)
}, interval);
}
}
}
}
总结:从降低CPU计算频次来说,肯定是 debounce 效果更好,因为它在整个高频回调期间,只会触发一次。
但 debounce 计算频次少,带来的影响就是可能会损失用户的体验。比如如果在 scrollViewDidScroll 中使用了 debounce 来触发某些渲染逻辑,那么用户如果一直在滚动屏幕,只有在用户松手后,才能把屏幕渲染出来,整个滚动过程是无法加载的。
考虑到现在CPU计算能力越来越强,在解决高频回调问题面前,我们可以优先选择 throttle 的方案 。