Appearance
防抖动与节流
本节目标:理解“防抖动与节流”的核心思路,并能把它用于实际开发或面试表达。 一些高频事件,如果正常绑定事件处理函数的话,有可能在很短的时间内多次连续触发事件,十分影响性能。 对这类事件要进行防抖动或者节流处理 高见高频事件:
- 鼠标的
scroll,resize,mouseomove touchMoveinput的onchange/oninput
防抖动 它的做法是限制下次函数调用之前必须等待的时间间隔。正确实现 debouncing 的方法是将若干个函数调用合成 一次,并在给定时间过去之后仅被调用一次。 高频事件不断触发时不调用,等高频事件停止触发后,在指定的时间后调用一次。 // 函数防抖的实现 function debounce(fn, wait) { let timer = null; return function () { let context = this, args = arguments; // 如果此时存在定时器的话,则取消之前的定时器重新记时 if (timer) { clearTimeout(timer); timer = null; } // 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); }; }
// 当用户滚动时被调用的函数
function foo() {
console.log('You are scrolling!');
}
// 在 debounce 中包装我们的函数,过 2 秒触发一次
let elem = document.getElementById('container');
elem.addEventListener('scroll', debounce(foo, 2000));更进一步,我们不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。 这里增加一个immediate参数来设置是否要立即执行: function debouce(func, delay, immediate) { var timer = null; return function () { var context = this; var args = arguments; if (timer) clearTimeout(timer); if (immediate) { //根据距离上次触发操作的时间是否到达delay来决定是否要现在执行函数 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); } } } 节流: 节流函数允许一个函数在规定的时间内只执行一次。 自己理解:高频事件触发时,会不断调用函数,但调用的频率由我们控制。 两者区别:
- 防抖只有高频事件停止后才会调用函数;
- 节流在高频事件触发的过程中会不断调用函数,但在规定时间内只会执行一次。
比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。 主要有两种实现方法:
- 时间戳
- 定时器
时间戳实现: 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(){ var context = this; var args = arguments; if (!timer) { timer = setTimeout(function () { func.apply(context, args); timer = null; }, delay); } } }
- 当第一次触发事件时,肯定不会立即执行函数,而是在
delay秒后才执行。 - 之后连续不断触发事件,也会每
delay秒执行一次。 - 当最后一次停止触发后,由于定时器的
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); } } } 需要在每个delay时间中一定会执行一次函数,因此在节流函数内部使用开始时间、当前时间与delay来计算remaining,当remaining\<=0时表示该执行函数了,如果还没到时间的话就设定在remaining时间后再触发。当然在remaining这段时间中如果又一次发生事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。
