为了不某个事件在较短的时间段内(称为 T)内连续触发从而引发的其对应的事件处理函数没必要要的连续执行的一种事件处理机制(高频触发事件解决方案)
debounce:当调用动做触发一段时间后,才会执行该动做,若在这段时间间隔内又调用此动做则将从新计算时间间隔。(屡次连续事件触发动做/最后一次触发以后的指定时间间隔执行回调函数)
throttle:预先设定一个执行周期,当调用动做的时刻大于等于执行周期则执行该动做,而后进入下一个新的时间周期。(每一个指定时间执行一次回调函数,能够指定时间间隔以前调用)javascript
一、throttle 保证了在每一个 T 内至少执行一次,而 debounce 没有这样的保证
二、每次事件触发时参考的时间点
,对于debounce
来是上一次事件触发的时间
而且在延时没有结束时会重置延时;throttle
是上一次 handler 执行的时间
而且在延时还没有结束时不会重置延时java
响应速度跟不上触发频率,每每会出现延迟,致使假死或者卡顿感git
空闲控制:全部操做最后一次性执行github
【简洁版】闭包
/** * @param fn {Function} 实际要执行的函数 * @param delay {Number} 延迟时间,也就是阈值,单位是毫秒(ms) * @return {Function} 返回一个“去弹跳”了的函数 */ function debounce(fn, delay) { // 定时器,用来 setTimeout var timer // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数 return function () { // 保存函数调用时的上下文和参数,传递给 fn var context = this var args = arguments // 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn clearTimeout(timer) // 当返回的函数被最后一次调用后(也就是用户中止了某个连续的操做), // 再过 delay 毫秒就执行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } }
【完整版】app
// immediate: 是否当即执行回调函数; 其它参数同上 function debounce(fn, wait, immediate) { let timer = null return function() { let args = [].slice.call(arguments) if (immediate && !timer) { fn.apply(this, args) } if (timer) clearTimeout(timer) timer = setTimeout(() => { //箭头函数,this指向外层环境 fn.apply(this, args) }, wait) } }
// 测试: var fn = function() { console.log('debounce..') } oDiv.addEventListener('click', debounce(fn, 3000))
固定频次:减小执行频次,每隔必定时间执行一次函数
【简洁版】测试
/** * 固定回调函数执行的频次 * @param fn {Function} 实际要执行的函数 * @param interval {Number} 执行间隔,单位是毫秒(ms) * * @return {Function} 返回一个“节流”函数 */ var throttle = function (fn, interval) { // 记录前一次时间 var last = +new Date() var timer = null // 包装完后返回 闭包函数 return function () { var current = +new Date() var args = [].slice.call(arguments, 0) var context = this // 首先清除定时器 clearTimeout(timer) // current 与last 间隔大于interval 执行一次fn // 在一个周期内 last相对固定 current一直再增长 // 这里能够保证调用很密集的状况下 current和last 必须是相隔interval 才会调用fn if (current - last >= interval) { fn.apply(context, args) last = current } else { // 若是没有大于间隔 添加定时器 // 这能够保证 即便后面没有再次触发 fn也会在规定的interval后被调用 timer = setTimeout(function() { fn.apply(context, args) last = current }, interval-(current - last)) } } }
【完整版】this
/** * 频率控制 返回函数连续调用时,func 执行频率限定为 次 / wait * 自动合并 data * * 若无 option 选项,或者同时为true,即 option.trailing !== false && option.leading !== false,在固定时间开始时刻调用一次回调,并每一个固定时间最后时刻调用回调 * 若 option.trailing !== false && option.leading === false, 每一个固定时间最后时刻调用回调 * 若 option.trailing === false && option.leading !== false, 只会在固定时间开始时刻调用一次回调 * 若同时为false 则不会被调用 * * @param {function} func 传入函数 * @param {number} wait 表示时间窗口的间隔 * @param {object} options 若是想忽略开始边界上的调用,传入{leading: false}。默认undefined * 若是想忽略结尾边界上的调用,传入{trailing: false}, 默认undefined * @return {function} 返回客户调用函数 */ function throttle (func, wait, options) { var context, args, result; var timeout = null; // 上次执行时间点 var previous = 0; if (!options) { options = {}; } // 延迟执行函数 function later () { // 若设定了开始边界不执行选项,上次执行时间始终为0 previous = options.leading === false ? 0 : Date.now(); timeout = null; result = func.apply(context, args); if (!timeout) { context = args = null; } } return function (handle, data) { var now = Date.now(); // 首次执行时,若是设定了开始边界不执行选项,将上次执行时间设定为当前时间。 if (!previous && options.leading === false) { previous = now; } // 延迟执行时间间隔 var remaining = wait - (now - previous); context = this; args = args ? [handle, Object.assign(args[1], data)] : [handle, data]; // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口 // remaining大于时间窗口wait,表示客户端系统时间被调整过 if (remaining <= 0 || remaining > wait) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); if (!timeout) { context = args = null; } // 若是延迟执行不存在,且没有设定结尾边界不执行选项 } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result } }
前二者 debounce 和 throttle 均可以按需使用;后二者确定是用 throttle.net
_.debounce = function(func, wait, immediate) { var timeout, result; var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); }; var debounced = restArgs(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; };
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; 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) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
【参考】
https://blog.coding.net/blog/...
https://github.com/lishengzxc...