debounce的特点是当事件快速连续不断触发时,动作只会执行一次。 延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。
debounce 的实现:
// 非立即执行
function debounce(func, wait) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
// 立即执行
function debounce(func,wait) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
// 结合
/**
* @desc 函数防抖
* @param func 函数
* @param wait 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
}
版本1: 周期内有新事件触发,清除旧定时器,重置新定时器;这种方法,需要高频的创建定时器。
/ 暴力版: 定时器期间,有新操作时,清空旧定时器,重设新定时器
var debounce = (fn, wait) => {
let timer, timeStamp=0;
let context, args;
let run = ()=>{
timer= setTimeout(()=>{
fn.apply(context,args);
},wait);
}
let clean = () => {
clearTimeout(timer);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
if(now-timeStamp < wait){
console.log('reset',now);
clean(); // clear running timer
run(); // reset new timer from current time
}else{
console.log('set',now);
run(); // last timer alreay executed, set a new timer
}
timeStamp=now;
}
}
版本2: 周期内有新事件触发时,重置定时器开始时间撮,定时器执行时,判断开始时间撮,若开始时间撮被推后,重新设定延时定时器。
/ 优化版: 定时器执行时,判断start time 是否向后推迟了,若是,设置延迟定时器
var debounce = (fn, wait) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval)=>{
timer= setTimeout(()=>{
let now = (new Date()).getTime();
let interval=now-startTimeStamp
if(interval<timerInterval){ // the timer start time has been reset, so the interval is less than timerInterval
console.log('debounce reset',timerInterval-interval);
startTimeStamp=now;
run(wait-interval); // reset timer for left time
}else{
fn.apply(context,args);
clearTimeout(timer);
timer=null;
}
},timerInterval);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
startTimeStamp=now;
if(!timer){
console.log('debounce set',wait);
run(wait); // last timer alreay executed, set a new timer
}
}
}
版本3: 在版本2基础上增加是否立即执行选项:
// 增加前缘触发功能
var debounce = (fn, wait, immediate=false) => {
let timer, startTimeStamp=0;
let context, args;
let run = (timerInterval)=>{
timer= setTimeout(()=>{
let now = (new Date()).getTime();
let interval=now-startTimeStamp
if(interval<timerInterval){ // the timer start time has been reset,so the interval is less than timerInterval
console.log('debounce reset',timerInterval-interval);
startTimeStamp=now;
run(wait-interval); // reset timer for left time
}else{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
}
},timerInterval);
}
return function(){
context=this;
args=arguments;
let now = (new Date()).getTime();
startTimeStamp=now; // set timer start time
if(!timer){
console.log('debounce set',wait);
if(immediate) {
fn.apply(context,args);
}
run(wait); // last timer alreay executed, set a new timer
}
}
}
throttling的特点在连续高频触发事件时,动作会被定期执行,响应平滑。
throttling 的实现:
版本1: 简单版
简单版: 定时器期间,只执行最后一次操作
var throttling = (fn, wait) => {
let timer;
let context, args;
let run = () => {
timer=setTimeout(()=>{
fn.apply(context,args);
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("throttle, set");
run();
}else{
console.log("throttle, ignore");
}
}
}
版本2: 增加前缘选项:(考虑情况较简单,复杂情况可参考underscope 的_.throttle)
/// 增加前缘
var throttling = (fn, wait, immediate) => {
let timer, timeStamp=0;
let context, args;
let run = () => {
timer=setTimeout(()=>{
if(!immediate){
fn.apply(context,args);
}
clearTimeout(timer);
timer=null;
},wait);
}
return function () {
context=this;
args=arguments;
if(!timer){
console.log("throttle, set");
if(immediate){
fn.apply(context,args);
}
run();
}else{
console.log("throttle, ignore");
}
}
}