throttle
节流:基于时间的频率来进行抽样更改。一个函数执行一次后,只有大于设定的执行周期后才会执行第二次。debounce
防抖:一段时间的不活动之后发布更改。一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。requestAnimationFrame
节流:基于requestAnimationFrame
来进行抽样更改。
该页面查看节流防抖区别
节流
throttle
节流
利用闭包和时间戳或定时器
Note:以下第一段代码考虑了this和上下文对象,再往下的代码均不考虑,只关注防抖节流核心逻辑
// 时间戳
// 以下代码考虑了this和上下文对象,再往下的代码均不考虑,只关注防抖节流核心逻辑。
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
let nowTime = Date.now();
if (nowTime - lastTime > delay) {
fn().apply(this, args);
lastTime = nowTime;
}
};
}
// 定时器
function throttle(fn, delay) {
let valid = true;
return function (...args) {
if (!valid) {
return false;
}
valid = false;
setTimeout(() => {
fn();
valid = true;
}, delay);
};
}
_.throttle
requestAnimationFrame
节流
以下内容来自于红宝书。
requestAnimationFrame
一般用作动画,用以通知浏览器某些JavaScript代码要执行动画了。它接收一个参数,此参数是一个要在重绘屏幕前调用的函数,函数就是修改 DOM 样式以反映下一次重绘有什么变化的地方。
支持这个方法的浏览器会暴露出作为钩子的回调队列。这个钩子就是浏览器在执行下一次重绘之前的一个点。这个回调队列是一个可修改的函数列表,包含应该在重绘之前调用的函数。每次调用requestAnimationFrame
都会在队列上推入一个回调函数,队列的长度没有限制。
其实这个回调队列的行为不一定跟动画有关。不过,通过requestAnimationFrame
递归地向队列 中加入回调函数,可以保证每次重绘最多只调用一次回调函数,如下代码
function updateProgress() {
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%") {
requestAnimationFrame(updateProgress);
}
}
requestAnimationFrame(updateProgress);
这是一个很棒的节流工具,如果想把函数的调用限制在每次重绘前发生,那么可以像这样下面把它封装到 requestAnimationFrame
调用中:
function expensiveOperation() {
console.log('Invoked at', Date.now());
}
window.addEventListener('scroll', () => {
window.requestAnimationFrame(expensiveOperation);
});
这样会把所有回调的执行集中在重绘钩子,但不会过滤掉每次重绘的多余调用。此时,定义一个标志变量,由回调设置其开关状态,就可以将多余的调用屏蔽:
let enqueued = false;
function expensiveOperation() {
console.log('Invoked at', Date.now());
enqueued = false;
}
window.addEventListener('scroll', () => {
if (!enqueued) {
enqueued = true;
window.requestAnimationFrame(expensiveOperation);
}
});
因为重绘是非常频繁的操作,所以这还算不上真正的节流。更好的办法是配合使用一个计时器来限 制操作执行的频率。这样,计时器可以限制实际的操作执行间隔,而requestAnimationFrame
控制在浏览器的哪个渲染周期中执行:
let enabled = true;
function expensiveOperation() {
console.log('Invoked at', Date.now());
}
window.addEventListener('scroll', () => {
if (enabled) {
enabled = false;
window.requestAnimationFrame(expensiveOperation);
window.setTimeout(() => enabled = true, 50);
}
});
防抖
debounce
防抖
function debounce(fn, delay) {
let timer = null;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn();
}, delay);
};
}