防抖和节流
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. 应用防抖
非立即执行版本
-
效果就是防抖的定义
-
let num = 1; const content = document.getElementById('content'); // 功能函数 function count() { this.innerHTML = num++; }; content.onclick = debounce(count,1000); // 防抖函数,非立即执行版本 function debounce(func, wait) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args) }, wait); } } 复制代码
-
防抖函数的代码使用这两行代码来获取 this 和 参数,是为了让 debounce 函数最终返回的函数 this 指向不变(即指向调用该函数的DOM元素)以及依旧能接受到 e 参数。
-
功能函数通过调用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) } } } 复制代码
-
第一次调用节流函数,还没有定时器,创建一个定时器,并在时间间隔结束时触发功能函数
-
在时间间隔内再次调用节流函数,由于定时器已经存在,不响应
-
当时间间隔结束后将本定时器标识符timeout清除,再创建一个定时器。
-
由于定时器标识符timeout被设置为null,再次调用节流函数便可再次触发。