防抖和节流其实是一种思想,是一种性能优化的方案,并非JavaScript独有,只不过JavaScript多运行于用户端(也就是前端),而用户端的交互是很频繁的,有不少的请求或交互实际上是重复的。而这就给了防抖和节流很大的用武之地。
① 防抖
防抖,顾名思义,防止手抖,曾经多少英雄豪杰,因为一时手抖擦枪走火而悔恨终身,也曾经多少富甲一方的商贾因为手抖多打了个0而家破人亡。
防抖的作用就是无论你是手滑了还是手抖了,还是打多了一个0,它仍然会沿着旧有的节奏走,从而防止手滑导致的一系列问题。
防抖的实现思路大体上是这样:
当持续触发事件时(比如用户输入时不断的键盘敲击),一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
可以看看下面的函数:
const debounce = (fn, wait) => {
let timeout = null;
return function () {
timeout !== null && clearTimeout(timeout);
timeout = setTimeout( fn, wait );
}
};
定时器一边在走,而这边继续监听触发,假如两次触发的时间间隔大于设置的防抖延迟,则这时候定时器里的函数已经执行了,而假如小于,定时器里的函数还没来得及执行,定时器就被清除了。
看一下调用,假如此时有一块DOM结构是这样的话。
<p class='shake'></p>
在调用上方的防抖函数debounce。如下:
const debounceDiv = () => console.log('debounceDiv'); // 定义一下需要触发的函数
document.querySelector('.shake').addEventListener('mousemove', debounce(debounceDiv, 300));
鼠标滑动的触发频率是很高的,可能不小心手抖鼠标滑动了一大下,这时候可能某个函数就被执行了好几十次,而处于中间的那些可能实际上我们并不需要,而通过防抖机制,尽管这个函数所处的环境被触发了几十次,但该函数实际上只会执行最后的那一次,这样就节省了资源,提高了性能。
再来看防抖实际应用中很多的例子 —— 输入。
假如你现在在编辑Markdown文本,或者搜索栏的一些输入,而这时候可能JavaScript做的一个监听动作就是你键盘的按下或抬起动作,但相比你打的字而言,你的键盘敲击速度实在是太快了,快到可能接近程序的一些操作或者搜索栏的一些结果联想,甚至比之还快,但就如这一个简单的打字而言,用户刚刚打了一个f,程序不管不顾立马执行,这没有问题,但可能用户是想打一个“防”字,他对f是几乎无察觉无认知的,而这部分假如还去执行结果,无疑是一种极大的浪费。
看看代码,由于这时候我们需要在一个恰当的时机,将用户输入的值打印到界面上,原来的debounce 函数需要稍微变一下,将监听的this值做下绑定:
变成:
const debounce = (fn, wait) => {
let timeout = null;
return function () {
timeout !== null && clearTimeout(timeout);
let _self = this;
timeout = setTimeout( () => fn.call(_self), wait);
}
};
再看一下应用:
const debounceInput = function () { document.querySelector('.shake').innerHTML = this.value; }
document.querySelector('.shake-input').addEventListener('keyup', debounce.call(this, debounceInput, 300));
这样10行代码左右也就简单的完成了我们的防抖。
② 节流
节流,也顾名思义 —— 任尓滔滔江水,我自涓涓细流。它就像一个水闸,或者说管道,尽管可能某段时间内这个事件触发了很多次,但在这里,统统无效,只会每间隔一定时间触发一次。它更贴切的一个描述可以看下图:
当持续触发事件时,保证一定时间段内只调用一次事件处理函数,这就是节流。
const throttle = (fn, delay) => {
let timeNode = Date.now();
return function () {
let time = Date.now();
if(time - timeNode >= delay) {
fn.call(this);
timeNode = Date.now();
}
}
}
const throttleDiv = () => console.log('throttleDiv');
const throttleInput = function () { document.querySelector('.throttle').innerHTML = this.value; }
document.querySelector('.throttle').addEventListener('mousemove', throttle(throttleDiv, 300));
document.querySelector('.throttle-input').addEventListener('keyup', throttle.call(this, throttleInput, 300));
最后吧,贴一下完整的测试用例。
html部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.container{
display: inline-block;
}
.shake, .throttle{
width: 300px;
height: 300px;
border: 1px solid #ccc;
word-break:break-all;
padding: 10px;
box-sizing: border-box;
}
.shake-input, .throttle-input{
width: 300px;
line-height: 35px;
border: 1px solid #ccc;
box-sizing: border-box;
}
</style>
</head>
<body>
<div class='container'>
<p class='shake'></p>
<input type="text" class='shake-input'>
</div>
<div class='container'>
<p class='throttle'></p>
<input type="text" class='throttle-input'>
</div>
<script src='./test.js'></script>
</body>
</html>
js部分
// 防抖
// 函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时
const debounce = (fn, wait) => {
let timeout = null;
return function () {
timeout !== null && clearTimeout(timeout);
let _self = this;
timeout = setTimeout( () => fn.call(_self), wait);
}
};
const debounceDiv = () => console.log('debounceDiv');
const debounceInput = function () { document.querySelector('.shake').innerHTML = this.value; }
document.querySelector('.shake').addEventListener('mousemove', debounce(debounceDiv, 300));
document.querySelector('.shake-input').addEventListener('keyup', debounce.call(this, debounceInput, 300));
// 节流
const throttle = (fn, delay) => {
let timeNode = Date.now();
return function () {
let time = Date.now();
if(time - timeNode >= delay) {
fn.call(this);
timeNode = Date.now();
}
}
}
const throttleDiv = () => console.log('throttleDiv');
const throttleInput = function () { document.querySelector('.throttle').innerHTML = this.value; }
document.querySelector('.throttle').addEventListener('mousemove', throttle(throttleDiv, 300));
document.querySelector('.throttle-input').addEventListener('keyup', throttle.call(this, throttleInput, 300));
好了,防抖和节流就先说到这了,以后有想到的再补充吧。