JS的防抖函数和节流函数:
防抖目的:
很容易理解,在项目中使用相应的触发函数的时候,会出现那种比如高频触发 搜索框实时发请求,onmousemove, resize, onscroll啥的, 我们不想这样频繁触发,只想在点击的时候才进行触发,所以我们就加以操作。进行控制
防抖函数用法:
such as: 我们鼠标滑动进行数据增加(案例是抄的)
<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
<script>
let num = 1;
let content = document.getElementById('content');
function count() {
content.innerHTML = num++;
};
content.onmousemove = count;
</script>
效果就是这样的 只要动鼠标就会增加。 但是我们就要在多长时间内才触发 不能频繁触发
解决方案:
- 非立即执行版:
短时间内多次触发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行。
// 非立即执行版
function debounce(func, wait) {
let timer;
return function() {
let context = this; // 注意 this 指向
let args = arguments; // arguments中存着e
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
// 触发
content.onmousemove = debounce(count,1000); //参数count 指的就是运行的方法体
- 立即执行版:
立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
// 立即执行版
function debounce(func, wait) {
let timer;
return function() {
let context = this; // 这边的 this 指向谁?
let args = arguments; // arguments中存着e
if (timer) clearTimeout(timer);
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait)
if (callNow) func.apply(context, args);
}
}
// 触发
content.onmousemove = debounce(count,1000); //参数count 指的就是运行的方法体
- 合成版:(传入true或false手动选择是立即还是非立即执行)
// 合成版
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行, false - 延迟执行
*/
function debounce(func, wait, immediate) {
let timer;
return function() {
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply
}, wait)
}
}
}
节流函数用法:
节流目的:
指连续触发事件但是在 n 秒中只执行一次函数。即 2n 秒内执行 2 次... 。节流如字面意思,会稀释函数的执行频率。
效果:在持续触发事件的过程中,函数会立即执行,并且每 1s 执行一次。
时间戳版本:
// 时间戳版
function throttle(func, wait) {
let 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);//参数count 指的就是运行的方法体
定时器版本:
// 定时器版
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)
}
}
}
//触发
content.onmousemove = throttle(count,1000);//参数count 指的就是运行的方法体
双剑合璧的版本:手动触发
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle(func, wait, type) {
if (type === 1) {
let previous = 0;
} else if (type === 2) {
let timeout;
}
return function() {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}
注意: 关于节流/防抖函数中 context(this) 的指向解析:
首先,在执行 throttle(count, 1000)
这行代码的时候,会有一个返回值,这个返回值是一个新的匿名函数,因此 content.onmousemove = throttle(count,1000);
这句话最终可以这样理解:
content.onmousemove = function() {
let now = Date.now();
let context = this;
let args = arguments;
...
console.log(this)
}
到这边为止,只是绑定了事件函数,还没有真正执行,而 this 的具体指向需要到真正运行时才能够确定下来。所以这个时候如果我们把前面的 content.onmousemove
替换成 var fn 并执行 fn fn() ,此时内部的 this 打印出来就会是 window 对象。
其次,当我们触发 onmousemove 事件的时候,才真正执行了上述的匿名函数,即 content.onmousemove() 。此时,上述的匿名函数的执行是通过 对象.函数名() 来完成的,那么函数内部的 this 自然指向 对象。
最后,匿名函数内部的 func 的调用方式如果是最普通的直接执行 func() ,那么 func 内部的 this 必然指向 window ,虽然在代码简单的情况下看不出什么异常(结果表现和正常一样),但是这将会是一个隐藏 bug,不得不注意啊!所以,我们通过匿名函数捕获 this,然后通过 func.apply() 的方式,来达到 content.onmousemove = func 这样的效果。