函数防抖(debounce)
- 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时;
- 如下图,持续触发 scroll 事件时,并不执行 handle 函数,当 1000 毫秒内没有触发 scroll 事件时,才会延时触发 scroll 事件;
应用场景
文字输入时的 keyup 事件:输入搜索关键词的时候,进行自动完成或者自动联想,这种情况下用户每敲击一次键盘就会触发一次 keyup 事件,此时用户可能连一个字都没有输入完成,此时可以使用 js 防抖,在用户停止输入的一段时间后 (如 500ms) 触发判断,进行自动联想或者自动搜索;
表单验证;
按钮提交事件;
浏览器的窗口缩放 (resize 事件);
缺点
如果事件在规定的时间间隔内被不断的触发,则调用方法会被不断的延迟;
防抖初步实现
当持续触发事件时,事件处理函数 handle 只在停止滚动 1000 毫秒之后才会调用一次,也就是说在持续触发事件的过程中,事件处理函数 handle 一直没有执行;
/**
* 防抖
* @param {*} fn 要执行的函数
* @param {*} wait 等待时间
* @param {*} immediate 是否立即执行
* @returns
*/
function debounce(fn, wait, immediate) {
// 记录 setTimeout 的返回值(唯一)
var timeout = null;
return function proxy(ev) {
var _this = this;
clearTimeout(timeout);
if (immediate) {
// 只要一直移动,timeout 则一直有值,则 callNow 为 fasle,就不会立即执行
var callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) fn.call(_this, ev);
} else {
timeout = setTimeout(function () {
fn.call(_this, ev);
}, wait);
}
}
}
// 处理函数
function handle(ev) {
console.log(Math.random());
console.log(this);
console.log(ev);
}
// leftNode:dom 节点绑定移动事件
leftNode.onmousemove = debounce(handle, 1000, true);
防抖函数中的返回值和取消操作
/**
* 防抖
* @param {*} fn 要执行的函数
* @param {*} wait 等待时间
* @param {*} immediate 是否立即执行
* @returns
*/
function debounce(fn, wait, immediate) {
// 记录 setTimeout 的返回值(唯一)
var timeout, result;
var proxy = function (ev) {
var _this = this;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 只要一直移动,timeout 则一直有值,则 callNow 为 fasle,就不会立即执行
var callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) result = fn.call(_this, ev);
} else {
timeout = setTimeout(function () {
fn.call(_this, ev);
}, wait);
}
return result;
}
// 取消操作
proxy.cancel = function () {
clearTimeout(timeout);
timeout = null;
}
return proxy;
}
// 处理函数
function handle(ev) {
console.log(Math.random());
console.log(this);
console.log(ev);
return '想要的结果';
}
var doSome = debounce(handle, 3000, false);
// 取消操作
btn.onclick = function () {
doSome.cancel();
}
// leftNode:dom 节点绑定移动事件
leftNode.onmousemove = doSome;
函数节流(throttle)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数;
节流通俗解释就比如水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴;
如下图,持续触发 scroll 事件时,并不立即执行 handle 函数,每隔 1000 毫秒才会执行一次 handle 函数;
应用场景
元素拖拽、移动时的 mousemove 事件;
射击游戏;
计算鼠标移动的距离;
监听 scroll 滚动事件;
时间戳实现
当高频事件触发时,第一次会立即触发,而后再怎么频繁地触发事件,也都是每 delay 时间才执行一次,最后一次事件不会被触发;
var throttle = function (func, delay) {
var prev = 0;
return function () {
var now = Date.now();
if (now - prev >= delay) {
func.apply(this, arguments);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
// leftNode:dom 节点绑定移动事件
leftNode.onmousemove = throttle(handle, 2000);
定时器实现
当高频事件触发时,第一次不会立即触发,而后再怎么频繁地触发事件,也都是每 delay 时间才执行一次,最后一次事件还会触发;
var throttle = function (func, delay) {
var context, args, timer = null;
return function () {
context = this;
args = arguments;
if (!timer) {
timer = setTimeout(function () {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
// leftNode:dom 节点绑定移动事件
leftNode.onmousemove = throttle(handle, 2000);
总结
-
函数防抖:将几次操作合并为一次操作进行,原理是维护一个计时器,规定在 delay 时间后触发函数,但是在 delay 时间内再次触发的话,就会取消之前的计时器而重新设置,这样一来,只有最后一次操作能被触发;
-
函数节流:使得一定时间内只触发一次函数,原理是通过判断是否到达一定时间来触发函数;
-
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数;函数防抖只是在最后一次事件后才触发一次函数;
第 5️⃣ 座大山:前端性能优化
上一篇