目录

1 什么是防抖和节流?
1.1 认识防抖 debounce 函数
1.2 认识节流 throttle 函数
2 实现防抖函数
2.1 基本实现v-1
2.2 this-参数v-2
2.3 可选是否立即执行v-3
2.4 取消功能v-4
2.5 返回值v-5(最终版本)
3. 实现节流函数
3.1 基本实现v-1
3.2 this-参数v-2
3.3 可选是否立即执行v-3
3.4 可选最后一次是否执行v-4(最终版本)

这题我会带大家先认识一下防抖和节流,然后在一步一步的实现一下,会有很多个版本,一步一步完善,但其实在实际的时候能基本实现v2版本就差不多了。

1 什么是防抖和节流?

1.1 认识防抖 debounce 函数

场景:在实际开发中,常常会碰到点击一个按钮请求网络接口的场景,这时用户如果因为手抖多点了几下按钮,就会出现短时间内多次请求接口的情况,实际上这会造成性能的消耗,我们其实只需要监听最后一次的按钮,但是我们并不知道哪一次会是最后一次,就需要做个延时触发的操作,比如这次点击之后的300毫秒内没再点击就视为最后一次。这就是防抖函数使用的场景

总结防抖函数的逻辑

  • 当事件触发时,相应的函数并不会立即触发,而是等待一定的时间;
  • 当事件密集触发时,函数的触发会被频繁的推迟
  • 只有等待了一段时间也没事件触发,才会真正的响应函数

1.2 认识节流 throttle 函数

场景:开发中我们会有这样的需求,在鼠标移动的时候做一些监听的逻辑比如发送网络请求,但是我们知道 document.onmousemove 监听鼠标移动事件触发频率是很高的,我们希望按照一定的频率触发,比如3秒请求一次。不管中间document.onmousemove 监听到多少次只执行一次。这就是节流函数的使用场景

总结节流函数的逻辑

  • 当事件触发时,会执行这个事件的响应函数;
  • 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行;
  • 不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的;

2 实现防抖函数

2.1 基本实现v-1

javascript
const debounceElement = document.getElementById("debounce"); const handleClick = function (e) { console.log("点击了一次"); }; // debounce防抖函数 function debounce(fn, delay) { // 定一个定时器对象,保存上一次的定时器 let timer = null // 真正执行的函数 function _debounce() { // 取消上一次的定时器 if (timer) { clearTimeout(timer); } // 延迟执行 timer = setTimeout(() => { fn() }, delay); } return _debounce; } debounceElement.onclick = debounce(handleClick, 300);

2.2 this-参数v-2

上面 handleClick 函数有两个问题,一个是 this 指向的是 window,但其实应该指向 debounceElement,还一个问题就是无法传递传递参数。

优化:

javascript
const debounceElement = document.getElementById("debounce"); const handleClick = function (e) { console.log("点击了一次", e, this); }; function debounce(fn, delay) { let timer = null; function _debounce(...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args) // 改变this指向 传递参数 }, delay); } return _debounce; } debounceElement.onclick = debounce(handleClick, 300);

2.3 可选是否立即执行v-3

有些时候我们想点击按钮的第一次就立即执行,该怎么做呢?

优化:

javascript
const debounceElement = document.getElementById("debounce"); const handleClick = function (e) { console.log("点击了一次", e, this); }; // 添加一个immediate参数 选择是否立即调用 function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; // 是否调用过 function _debounce(...args) { if (timer) { clearTimeout(timer); } // 如果是第一次调用 立即执行 if (immediate && !isInvoke) { fn.apply(this.args); isInvoke = true; } else { // 如果不是第一次调用 延迟执行 执行完重置isInvoke timer = setTimeout(() => { fn.apply(this, args); isInvoke = false; }, delay); } } return _debounce; } debounceElement.onclick = debounce(handleClick, 300, true);

2.4 取消功能v-4

有些时候我们设置延迟时间很长,在这段时间内想取消之前点击按钮的事件该怎么做呢?

优化:

javascript
const debounceElement = document.getElementById("debounce"); const cancelElemetnt = document.getElementById("cancel"); const handleClick = function (e) { console.log("点击了一次", e, this); }; function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; function _debounce(...args) { if (timer) { clearTimeout(timer); } if (immediate && !isInvoke) { fn.apply(this.args); isInvoke = true; } else { timer = setTimeout(() => { fn.apply(this, args); isInvoke = false; }, delay); } } // 在_debounce新增一个cancel方法 用来取消定时器 _debounce.cancel = function () { clearTimeout(timer); timer = null; }; return _debounce; } const debonceClick = debounce(handleClick, 5000, false); debounceElement.onclick = debonceClick; cancelElemetnt.onclick = function () { console.log("取消了事件"); debonceClick.cancel(); };

2.5 返回值v-5(最终版本)

最后一个问题,上面 handleClick 如果有返回值我们应该怎么接收到呢

优化:用Promise回调

javascript
const debounceElement = document.getElementById("debounce"); const cancelElemetnt = document.getElementById("cancel"); const handleClick = function (e) { console.log("点击了一次", e, this); return "handleClick返回值"; }; function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; function _debounce(...args) { return new Promise((resolve, reject) => { if (timer) clearTimeout(timer); if (immediate && !isInvoke) { try { const result = fn.apply(this, args); isInvoke = true; resolve(result); // 正确的回调 } catch (err) { reject(err); // 错误的回调 } } else { timer = setTimeout(() => { try { const result = fn.apply(this, args); isInvoke = false; resolve(result); // 正确的回调 } catch (err) { reject(err); // 错误的回调 } }, delay); } }); } _debounce.cancel = function () { clearTimeout(timer); timer = null; }; return _debounce; } const debonceClick = debounce(handleClick, 300, true); // 创建一个debonceCallBack用于测试返回的值 const debonceCallBack = function (...args) { debonceClick.apply(this, args).then((res) => { console.log({ res }); }); }; debounceElement.onclick = debonceCallBack; cancelElemetnt.onclick = () => { console.log("取消了事件"); debonceClick.cancel(); };

3. 实现节流函数

3.1 基本实现v-1

这里说一下最主要的逻辑,只要 这次监听鼠标移动事件处触发的时间减去上次触发的时间大于我们设置的间隔就执行想要执行的操作就行了

nowTimelastTime>intervalnowTime - lastTime > interval

nowTime:这次监听鼠标移动事件处触发的时间

lastTime:监听鼠标移动事件处触发的时间

interval:我们设置的间隔

javascript
const handleMove = () => { console.log("监听了一次鼠标移动事件"); }; const throttle = function (fn, interval) { // 记录当前事件触发的时间 let nowTime; // 记录上次触发的时间 let lastTime = 0; // 事件触发时,真正执行的函数 function _throttle() { // 获取当前触发的时间 nowTime = new Date().getTime(); // 当前触发时间减去上次触发时间大于设定间隔 if (nowTime - lastTime > interval) { fn(); lastTime = nowTime; } } return _throttle; }; document.onmousemove = throttle(handleMove, 1000);

3.2 this-参数v-2

和防抖一样,上面的代码也会有 this 指向问题 以及 参数传递

优化:

javascript
const handleMove = (e) => { console.log("监听了一次鼠标移动事件", e, this); }; const throttle = function (fn, interval) { let nowTime; let lastTime = 0; function _throttle(...args) { nowTime = new Date().getTime(); if (nowTime - lastTime > interval) { fn.apply(this, args); lastTime = nowTime; } } return _throttle; }; document.onmousemove = throttle(handleMove, 1000);

3.3 可选是否立即执行v-3

上面的函数第一次默认是立即触发的,如果我们想自己设定第一次是否立即触发该怎么做呢?

优化:

javascript
const handleMove = (e) => { console.log("监听了一次鼠标移动事件", e, this); }; const throttle = function (fn, interval, leading = true) { let nowTime; let lastTime = 0; function _throttle(...args) { nowTime = new Date().getTime(); // leading为flase表示不希望立即执行函数 // lastTime为0表示函数没执行过 if (!leading && lastTime === 0) { lastTime = nowTime; } if (nowTime - lastTime > interval) { fn.apply(this, args); lastTime = nowTime; } } return _throttle; }; document.onmousemove = throttle(handleMove, 3000, false);

3.4 可选最后一次是否执行v-4(最终版本)

如果最后一次监听的移动事件与上一次执行的时间不到设定的时间间隔,函数是不会执行的,但是有时我们希望无论到没到设定的时间间隔都能执行函数,该怎么做呢?

我们的逻辑是:因为我们不知道哪一次会是最后一次,所以每次都设置一个定时器,定时器的时间间隔是距离下一次执行函数的时间;然后在每次进来都清除上次的定时器。这样就能保证如果这一次是最后一次,那么等到下一次执行函数的时候就必定会执行最后一次设定的定时器。

javascript
const handleMove = (e) => { console.log("监听了一次鼠标移动事件", e, this); }; // trailing用来选择最后一次是否执行 const throttle = function (fn,interval,leading = true,trailing = false) { let nowTime; let lastTime = 0; let timer; function _throttle(...args) { nowTime = new Date().getTime(); // leading为flase表示不希望立即执行函数 // lastTime为0表示函数没执行过 if (!leading && lastTime === 0) { lastTime = nowTime; } if (timer) { clearTimeout(timer); timer = null; } if (nowTime - lastTime >= interval) { fn.apply(this, args); lastTime = nowTime; return; } // 如果选择了最后一次执行 就设置一个定时器 if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; lastTime = 0; }, interval - (nowTime - lastTime)); } } return _throttle; }; document.onmousemove = throttle(handleMove, 3000, true, true);
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:叶继伟

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!