- 防抖
- 定义
- 应用场景:
- 实现思路:
- _.debounce 源码
- 节流
- 定义
- 应用场景:
- 实现思路:
- _.throttle
- 防抖和节流的区别
- scroll 事件下的区别
- mousemove 事件下的区别
防抖
定义
强制函数在固定时间
只执行一次,多余执行无效
应用场景:
- 按钮的防二次点击操作 视频参考
实现思路:
- 开启一个延时器,只要延时器还在,不管怎么点击都不执行回调函数
- 一旦延时器结束并设置为
null
,就可以再次点击- 需要考虑的点:
this
指向,事件源对象
的获取(参数)
//ES6
function debounce(fn, delay, timer = null) {
return function (...args) {
clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), delay);
};
}
//ES3 推荐
function debounce(fn, delay, timer = null) {
return function () {
let self = this
let arg = arguments
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(self, arg);
}, delay);
};
}
_.debounce 源码
// 使用 underscore 的源码来解释防抖动
/**
* underscore 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
// 现在和上一次时间戳比较
var last = _.now() - timestamp;
// 如果当前间隔时间少于设定时间且大于0就重新设置定时器
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
// 否则的话就是时间到了执行回调函数
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
// 获得时间戳
timestamp = _.now();
// 如果定时器不存在且立即执行函数
var callNow = immediate && !timeout;
// 如果定时器不存在就创建一个
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
// 如果需要立即执行函数的话 通过 apply 执行
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
节流
定义
强制函数以规定频率
执行
应用场景:
- scroll事件,每隔一秒计算一次位置信息
- 浏览器播放事件,每一秒计算一次进度信息
实现思路:
- 设置一个lock,只要lock还在,就不执行
延时器里
的回调函数- 每隔一段时间,就把lock打开,执行延时器中的回调函数
- 需要考虑的点:
this
指向,事件源对象
的获取(参数)
//ES6
function throttle(fn, delay) {
let lock = false;
return function (...args) {
if (lock) return;
fn.apply(this, args);
lock = true;
setTimeout(() => (lock = false), delay);
};
}
//ES3
function throttle(fn, delay) {
var lock = false;
return function () {
if (lock) return;
fn.apply(this, arguments);
lock = false;
setTimeout(() => (lock = true), delay);
};
}
_.throttle
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
防抖和节流的区别
防抖:防止抖动,单位时间内事件触发会被重置,避免事件被触发多次,重在清零
节流:控制流量,单位时间内事件只能触发一次,重在开关锁
scroll 事件下的区别
视频参考
mousemove 事件下的区别
视频参考