Open Nomadcheng opened 3 years ago
debounce:把触发非常频繁的事件(比如按键)合并成一次执行。
throttle:保证每 X 毫秒恒定的执行次数,比如每 200ms 检查下滚动位置,并触发 CSS 动画。
requestAnimationFrame:可替代 throttle ,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。注意:IE9 不支持。
防抖和节流的显著区别 在于,节流在指定时间间隔内只会执行一次任务,而防抖则是任务之间间隔超过一定阈值才回执行一次任务。
防抖函数 debounce 指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次,即触发频繁的事件合并成一次执行(如键盘输入)。
debounce
实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
cosnt debounce = function(func, wait, immediate) { let timer = null; // 定时器计时结束后 // 1、清空计时器,使之不影响下次连续事件的触发 // 2、触发执行 func let later = function(context, args) { timer = null; return func.apply(context, args); } let handler = function(...args) { const context = this; if (timer) { clearTimeout(timer); } let result; // immediate为true则首次立马执行函数,不需要等wait时间 // 根据 timer 是否为空可以判断是否是首次触发 if (immediate) { let shouldCall = !timer; timer = setTimeout(later.bind(null, context, args), wait); if (shouldCall) { result = func.apply(context, args); } } else { timer = setTimeout(later.bind(null, context, args), wait); } return; } return handler; } // DEMO1 // 执行 debounce 函数返回新函数 const betterFn = debounce(() => console.log('fn 防抖执行了'), 1000) // 停止滑动 1 秒后执行函数 () => console.log('fn 防抖执行了') document.addEventListener('scroll', betterFn) // DEMO2 // 执行 debounce 函数返回新函数 const betterFn = debounce(() => console.log('fn 防抖执行了'), 1000, true) // 立即执行函数 () => console.log('fn 防抖执行了') // 停止滑动 1 秒后再次执行函数 () => console.log('fn 防抖执行了') document.addEventListener('scroll', betterFn)
保证每 X 毫秒恒定的执行次数,比如每 200ms 处理一次页面滚动
实现方案有以下两种:
function throttle(func, threshold = 200) { // 上一次执行fn的时间 let previous = 0; return function(...args) { // 获取当前时间 let now = +new Date(); // 将当前时间和上一次执行函数的时间进行对比 // 大于等待时间就把 previous 设置为当前时间并执行函数 fn if (now - previous > threshold) { previous = now; func.apply(this, args); } } }
scroll
1000ms
handler
function throttle(func, threshold = 200) { // 设置一个定时器 let timer = null; return function(...args) { // 如果定时器存在则返回 if (timer) return; timer = setTimeout(() => { // 执行函数并将定时器置为null timer = null; func.apply(this, args); }, threshold); } }
我们再看下上面两种定时器的响应时间,第一种定时器是立马执行,第二种定时器是需要等待threshold之后再执行,那我们是否可以实现一个既可以立马执行,也可以等待threshold后再执行的函数呢,还可以立马执行并且在最后再执行一次的函数呢?
结合以上两种方式,我们加入了leading跟trailing参数来判断
leading
false
trailing
const throttle = function(func, wait, options) { var timeout, context, args, result; // 上一次执行回调的时间戳 var previous = 0; // 无传入参数时,初始化 options 为空对象 if (!options) options = {}; var later = function() { // 当设置 { leading: false } 时 // 每次触发回调函数后设置 previous 为 0 // 不然为当前时间 previous = options.leading === false ? 0 : _.now(); // 防止内存泄漏,置为 null 便于后面根据 !timeout 设置新的 timeout timeout = null; // 执行函数 result = func.apply(context, args); if (!timeout) context = args = null; }; // 每次触发事件回调都执行这个函数 // 函数内判断是否执行 func // func 才是我们业务层代码想要执行的函数 var throttled = function() { // 记录当前时间 var now = _.now(); // 第一次执行时(此时 previous 为 0,之后为上一次时间戳) // 并且设置了 { leading: false }(表示第一次回调不执行) // 此时设置 previous 为当前值,表示刚执行过,本次就不执行了 if (!previous && options.leading === false) previous = now; // 距离下次触发 func 还需要等待的时间 var remaining = wait - (now - previous); context = this; args = arguments; // 要么是到了间隔时间了,随即触发方法(remaining <= 0) // 要么是没有传入 {leading: false},且第一次触发回调,即立即触发 // 此时 previous 为 0,wait - (now - previous) 也满足 <= 0 // 之后便会把 previous 值迅速置为 now if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // clearTimeout(timeout) 并不会把 timeout 设为 null // 手动设置,便于后续判断 timeout = null; } // 设置 previous 为当前时间 previous = now; // 执行 func 函数 result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 最后一次需要触发的情况 // 如果已经存在一个定时器,则不会进入该 if 分支 // 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支 // 间隔 remaining milliseconds 后触发 later 方法 timeout = setTimeout(later, remaining); } return result; }; return throttled; }; // DEMO // 执行 throttle 函数返回新函数 const betterFn = throttle(() => console.log('fn 函数执行了'), 1000) // 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn setInterval(betterFn, 10)
可替代 throttle ,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。注意:IE9 不支持。
let start = null; const element = document.getElementById('SomeElementYouWantToAnimate'); element.style.position = 'absolute'; const step = function(timestamp) { if (!start) start = timestamp; let progress = timestamp - start; element.style.left = Math.min(progress / 10, 200) + 'px'; if (progress < 2000) { window.requestAnimationFrame(step); } } window.requestAnimationFrame(step);
防抖节流
概念
debounce:把触发非常频繁的事件(比如按键)合并成一次执行。
throttle:保证每 X 毫秒恒定的执行次数,比如每 200ms 检查下滚动位置,并触发 CSS 动画。
requestAnimationFrame:可替代 throttle ,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。注意:IE9 不支持。
防抖和节流的显著区别 在于,节流在指定时间间隔内只会执行一次任务,而防抖则是任务之间间隔超过一定阈值才回执行一次任务。
debounce
防抖函数
debounce
指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次,即触发频繁的事件合并成一次执行(如键盘输入)。实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
throttle
保证每 X 毫秒恒定的执行次数,比如每 200ms 处理一次页面滚动
实现方案有以下两种:
scroll
事件刚触发时,打印一个 hello world,然后设置个1000ms
的定时器,此后每次触发scroll
事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler
被清除,然后重新设置定时器。我们再看下上面两种定时器的响应时间,第一种定时器是立马执行,第二种定时器是需要等待threshold之后再执行,那我们是否可以实现一个既可以立马执行,也可以等待threshold后再执行的函数呢,还可以立马执行并且在最后再执行一次的函数呢?
结合以上两种方式,我们加入了leading跟trailing参数来判断
leading
参数,false
时忽略)trailing
参数,false
时忽略)requestAnimationFrame
可替代 throttle ,函数需要重新计算和渲染屏幕上的元素时,想保证动画或变化的平滑性,可以用它。注意:IE9 不支持。