前端提升技巧:防抖与节流的性能优化实践
防抖和节流
2021/5/5
参考文章
一. 为什么要用到防抖节流
-
当函数绑定一些持续触发的事件如:resize、scroll、mousemove ,键盘输入,多次快速click等等,
-
如果每次触发都要执行一次函数,会带来性能下降,资源请求太频繁等问题
-
就比如这样
-
div{ height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px; }
-
<div id="content"></div> <script> let num = 1; const content = document.getElementById('content'); function count() { content.innerHTML = num++; }; content.onmousemove = count; </script>
-
-
上面的效果,只要鼠标在div区域内一移动,count函数就会被执行,数字就会增加。
-
这时就可以用上防抖和节流了
二. 防抖
1. 什么是防抖?
- 所谓防抖,就是指触发事件后 n 秒后才执行函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
2. 应用防抖
非立即执行版本
/**
* 非立即执行防抖函数
* @param {Function} func
* @param {number} delay
* @returns
*/
function debounce(func, delay) {
let timeout
return function () {
const _this = this
const args = [...arguments]
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
func.apply(_this, args)
}, delay)
}
}
使用示例一
/**
* 模拟打印index
* @param {number} index
*/
function log(index) {
console.log("index:", index)
}
// 延迟一秒打印
const logFun = debounce(log, 1000)
// 索引
let index1 = 1
// 模拟100毫秒触发一次
const interval1 = setInterval(() => {
logFun(index1++)
}, 100)
// 模拟1秒后停止触发
setTimeout(() => {
clearInterval(interval1)
}, 1000)
使用实例二
/**
* 模拟修改car对象的frameNum属性
* @param {string} frameNum 车架号
*/
function changeframeNum(frameNum) {
this.frameNum = frameNum
console.log(car)
}
const car = {
name: 'byd',
frameNum: '22112',
changeframeNum: debounce(changeframeNum, 2000)
}
let index2 = 0
const interval2 = setInterval(() => {
car.changeframeNum('2023' + index2++)
}, 100)
setTimeout(() => {
clearInterval(interval2)
}, 1000)
-
暂存 this 和 参数。
-
功能函数通过调用apply方法将debounce 函数最终返回的函数 this 指向绑定给自身。
-
不管是setTimeout还是setInterval,都会有一个返回值。这个返回值是一个数字,代表当前是在浏览器中设置的第几个定时器(返回的是定时器序号)。如果在编辑器返回值可能是个对象。
-
每次触发调用防抖函数,如果之前的定时器(以定时器序号作为标识符)还在。就清除前面一个定时器,并开启一个新的定时器。
-
定时器即使清除了,其返回值也不会清除,之后设置定时器的返回值也会在其返回值的基础上继续向后排。
立即执行版
- 触发事件后函数会立即执行,n 秒内触发事件不会执行功能函数下一次调用,n秒后再次触发才会再次执行功能函数。
// 防抖函数,立即执行版本
function debounce(func, wait) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) func.apply(context, args);
};
}
-
只有在callnow值为true时才会触发功能函数
-
第一次调用防抖函数,还没有定时器,触发功能函数
-
在时间间隔内如果再次调用防抖函数,这时已经有定时器在了,即使清除定时器,但定时器标识符timeout还在,所以callNow的值是false,不能触发功能函数
-
等定时器间隔过去后执行定时器里的代码,将定时器标识符timeout设置为null
-
之后再调用防抖函数才会让callNow的值为true,触发功能函数
合并
- 增加一个形参作为判断
// 防抖函数,合并版本,immediate为true时为立即执行
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
}
三. 节流
1. 什么是节流?
- **所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。**节流会稀释函数的执行频率。
2. 应用节流
时间戳
-
function throttle(func, wait) { var previous = 0; return function() { let now = Date.now(); let context = this; let args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; } } } content.onmousemove = throttle(count,1000);
-
时间戳版的功能函数触发是在时间段内开始的时候
定时器
function throttle(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
使用示例
function log(index) {
console.log('index: ', index)
}
const logT = throttle(log, 1000)
let num = 0
// 模拟100毫秒触发一次
setInterval(() => {
logT(num++)
}, 100)
-
第一次调用节流函数,还没有定时器,创建一个定时器,并在时间间隔结束时触发功能函数
-
在时间间隔内再次调用节流函数,由于定时器已经存在,不响应
-
当时间间隔结束后将本定时器标识符timeout清除,再创建一个定时器。
-
由于定时器标识符timeout被设置为null,再次调用节流函数便可再次触发。