Appearance
防抖动与节流
本节目标:理解“防抖动与节流”的核心思路,并能把它用于实际开发或面试表达。 针对一些会频繁触发的事件如
scroll、resize,如果正常绑定事件处理函数的话,有可能在很短的时间内多次连续触发事件,十分影响性能。对这类事件要进行防抖动或者节流处理 高见高频事件:
鼠标的
scroll,resize,mouseomovetouchMoveinput的onchange/oninput
**requestAnimationFrame** **节流**:基于 requestAnimationFrame 的抽样更改 (例如 [raf-schd](https://react.docschina.org/docs/%5B%60raf-schd%60%5D\(https:/github.com/alexreardon/raf-schd\)))防抖动 (debounce): 高频事件不断触发时不调用事件处理函数,等高频事件停止触发后,在指定的时间后调用一次。作为可选方案,也可以在高频事件第一次触发时也调用一次事件处理函数。 **原理:**高频事件的回调函数会清除掉之前创建的计时器,所以计时器中的函数不会执行;只有最事件的回调函数创建的计时器不会被清除,事件处理函数执行一次。 // 将会包装事件的 debounce 函数 function debounce(fn, delay) { // 维护一个 timer let timer = null; return function () { // 一、绑定this和获取参数 let context = this; let args = arguments; // 二、清除计时器 clearTimeout(timer); // 三、设定新计时器 timer = setTimeout(function () { fn.apply(context, args); }, delay); } }
**使用方法:**事通过de件回调函数通过 // 当用户滚动时被调用的函数 function foo() { console.log('You are scrolling!'); } // 在 debounce 中包装我们的函数,过 2 秒触发一次 let elem = document.getElementById('container'); elem.addEventListener('scroll', debounce(foo, 2000));
可选方案:在高频事件第一次触发时也调用一次事件处理函数。 这里通过增加一个immediate参数来设置是否要立即执行还是按之前防抖函数的设计执行: function debouce(func, delay, immediate) { var timer = null; return function () { var context = this; var args = arguments; if (timer) clearTimeout(timer); if (immediate) { // 只有首次触发doNow才为true var doNow = !timer; //每一次都重新设置timer,但只有事件停止delay秒后才会执行回调 timer = setTimeout(function () { timer = null; }, delay); //立即执行 if (doNow) { func.apply(context, args); } } else { timer = setTimeout(function () { func.apply(context, args); }, delay); } } } 节流 (throttle)****: 高频事件的事件处理函数,以设定的时间为周期反复调用,但在每个周期内只会调用一次。 主要有两种实现方法:
- 时间戳
- 定时器
时间戳实现: var throttle = function (func, delay) { // 一、定义变量记录上一次调用的时间戳 var prev = Date.now(); return function () { var context = this; var args = arguments; var now = Date.now(); if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } } }
定时器实现: 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行;直到delay秒后,定时器执行执行函数,清空定时器,这样就可以设置下个定时器。 var throttle = function (func, delay) { // 一、定义事件处理函数共享的变量:一个计时器 var timer = null; // 二、返回一个事件处理函数 return funtion(){ // 三、记录this和参数 var context = this; var args = arguments; // 四、如果之前有计时器,什么都不做;没有计时器时,创建一个计时器 if (!timer) { timer = setTimeout(function () { func.apply(context, args); // 五、回调函数执行完毕后,清除计时器,这样可以重新计时 timer = null; }, delay); } } }
可以综合使用时间戳与定时器,完成一个事件触发时立即执行,触发完毕还能执行一次的节流函数: var throttle = function (func, delay) { var timer = null; var startTime = Date.now(); return function () { var curTime = Date.now(); var remaining = delay - (curTime - startTime); var context = this; var args = arguments; clearTimeout(timer); if (remaining <= 0) { func.apply(context, args); startTime = Date.now(); } else { timer = setTimeout(func, remaining); } } }
- 满足时间间隔就调用函数;
- 如果不满足时间间隔,就延后一些时间调用,但如果在这过程中事件又被触发了,则清除计时器,就当什么也没发生。这样,只有最后一次事件的触发才会在延后的时间里调用函数。
