# 节流防抖
# 1、debounce 防抖
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次。
- 实现方式:使用setTimeout实现,再触发的时候清除定时器, 重新计时
防抖是一段时候后触发, 如果时间段内再次触发则重新计时。触发延后。因为防抖要的是最后的那个状态, 比如输入的结果是最后的状态, resize也是需要根据最后不动的宽高来决定其他元素的布局。
# 2、throttle 节流
: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有第一次生效。
- 鼠标不断点击触发,mousedown(单位时间内只触发一次),防止触发多次事件
- 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
- 实现方式:可以使用一个变量存储事件是否允许执行,如果允许则执行函数并且把该变量修改为不允许,并使用定时器在规定时间后重置变量为允许,或者等异步操作结果返回再重置变量为允许。
节流是一开始就会触发一次, 一定时间内如果再次触发无效。 比如滚动到底部加载更多, 滚到那个距离就直接触发, 后面继续滚动不再触发。
# 3、防抖函数实现
# 3.1 利用setTimeout实现一个简易的防抖函数
function debounce(fn, delay = 0) {
let _timer = 0;
return function (...args) {
if (_timer) {
clearTimeout(_timer);
}
_timer = setTimeout(() => {
try {
fn(...args);
} catch (error) {
throw error;
}
_timer = 0;
}, delay);
};
}
const foo = debounce(a => {
console.log(a);
}, 100);
foo(1);
foo(2);
foo(3);
// 最后只会输出3
# 3.2 在上述的基础之上关注this,新增immediate 参数
function debounce(fn, delay = 0, immediate = false) {
let timer = 0,
result;
let _immediate = immediate;
return function (...args) {
let context = this;
if (_immediate && !timer) {
_immediate = false;
result = fn.apply(context, args);
}
if (timer) {
clearTimeout(timer);
timer = 0;
}
timer = setTimeout(() => {
timer = 0;
fn.apply(context, args);
}, delay);
return result;
};
}
const obj = { name: 'obj' };
obj.print = debounce(
function (a, b) {
console.log(a, b, this);
return a + b;
},
100,
true
);
obj.print(1, 1);
obj.print(2, 2);
obj.print(3, 3);
immediate
设置为true
会在初次调用时先执行一次, 这时候尽量返回回调函数的返回值。再利用apply
实现this
绑定。
# 3.3 使用 requestAnimationFrame 代替setTimeout
// delayCallback 的delay参数传入0的话, 回调函数会被同步执行
function delayCallback(callback, delay = 0) {
let start = Date.now();
let requestId;
function fn() {
let end = Date.now();
if (end - start >= delay) {
callback();
} else {
requestId = requestAnimationFrame(fn);
// window.cancelAnimationFrame(requestId)
}
return requestId;
}
return fn();
}
function debounce(fn, delay = 0, immediate = false) {
let timer = 0;
let _immediate = immediate;
return function (...args) {
let context = this;
if (_immediate && !timer) {
_immediate = false;
fn.apply(context, args);
}
if (timer) {
window.cancelAnimationFrame(timer);
timer = 0;
}
timer = delayCallback(() => {
timer = 0;
fn.apply(context, args);
}, delay);
};
}
# 3.4 setTimeout和requestAnimationFrame兼容使用
function delayCallback(callback, delay) {
let start = Date.now();
let requestId;
function fn() {
let end = Date.now();
if (end - start >= delay) {
callback();
} else {
requestId = requestAnimationFrame(fn);
// window.cancelAnimationFrame(requestId)
}
return requestId;
}
return fn();
}
function debounce(fn, delay = 0, immediate = false) {
let timer = 0;
let _immediate = immediate;
const useRAF = delay !== 0 && typeof window.requestAnimationFrame === 'function';
return function (...args) {
let context = this;
if (_immediate && !timer) {
_immediate = false;
fn.apply(context, args);
}
if (timer) {
//
if (useRAF) {
window.cancelAnimationFrame(timer);
} else {
clearTimeout(timer);
}
timer = 0;
}
let nextTick;
if (useRAF) {
nextTick = delayCallback;
} else {
nextTick = setTimeout;
}
timer = nextTick(() => {
timer = 0;
fn.apply(context, args);
}, delay);
};
}
其实大多数情况下, 第二种方式就足够使用了
# 3.5 实现cancel取消功能,取消防抖设定
function debounce(fn, delay = 0, immediate = false) {
let timer = 0,
result,
disabled = false,
_immediate = immediate;
const _debounce = function (...args) {
let context = this;
if (disabled) {
return fn.apply(context, args);
}
if (_immediate && !timer) {
_immediate = false;
result = fn.apply(context, args);
}
if (timer) {
clearTimeout(timer);
timer = 0;
}
timer = setTimeout(() => {
timer = 0;
fn.apply(context, args);
}, delay);
return result;
};
// 取消防抖
_debounce.cancel = function () {
clearTimeout(timer);
timer = 0;
disabled = true;
};
return _debounce;
}
用法举例
const useMove = debounce(e => {
console.log(e.clientX);
}, 100);
document.addEventListener('mousemove', useMove);
setTimeout(() => {
useMove.cancel();
}, 2000);
# 4、节流函数实现
# 4.1 使用setTimeout实现简易的节流函数
function throttle(fn, delay = 0) {
let timer = 0;
let flag = true;
let result;
return function (...args) {
const context = this;
if (flag) {
result = fn.apply(context, args);
flag = false;
} else {
result = undefined;
}
timer = setTimeout(() => {
flag = true;
}, delay);
return result;
};
}
其他大部分时候的异步开销节流, 可以自己定义一个变量来控制。
# 4.2 使用时间值来判断
function throttle(fn, delay = 0) {
let startTime = 0;
let result;
return function (...args) {
const context = this;
const now = Date.now();
if (now - startTime >= delay) {
result = fn.apply(context, args);
startTime = now;
} else {
result = undefined;
}
return result;
};
}
用法举例
const foo = throttle(function (a) {
console.log(a);
return a;
}, 100);
foo(1);
foo(2);
setTimeout(() => {
foo(3);
}, 200);
← Set, Map, for of 预编译 →