函数防抖和函数节流
前言
这里介绍一下函数防抖和函数节流,主要用js 举例。当你看完的时候你就发现自己以前就用过,只是不知道它的专业术语,好吧,让我们来了解一下。
正文
函数防抖
什么是函数防抖呢?
假设在这样一种情况下,比如说我们这样那样希望在滚动后,做某些操作,但是呢?
这里分析一下,就是要在滚动后,什么是滚动后呢?就是滚动不动了,那么就是滚动后。
我们可以监听滚动事件:
//给页面绑定滑轮滚动事件
if (document.addEventListener) {//firefox
document.addEventListener('onscroll', scrollFunc, false);
}
//滚动滑轮触发scrollFunc方法 //ie 谷歌
window.onmousewheel = document.onscroll= scrollFunc;
function scrollFunc{
//方法
}
如果我们滚动一下就去执行我们的事件,那么就会造成很多事情,比如说如卡顿,再比如说执行多次不符合我们的预期,也就是功能没有实现。
那么这个问题,就是因为我们没有做到滚动后。有些系统没有提供滚动后的事件,那么我们得自己实现。
那么就得回到滚动后这个事件中来,滚动后就是滚动后一段时间内不动,那么就是滚动后。
那么可以这样写:
function debounce(fn,wait){
var timer = null;
return function(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(fn,wait);
}
}
function handle(){
console.log("滚动结束");
}
window.addEventListener("onscroll",debounce(handle,1000));
这里可能有大家困惑的一个问题,debounce 多次执行,debounce 中的timer 没有执行不是会为空吗?那么(timer !== null) 不是不成立吗?
这里就需要我们看仔细了,我们多次执行的是:
function(){
if(timer !== null){
clearTimeout(timer);
}
timer = setTimeout(fn,wait);
}
而不是debounce,因为闭包原因,那么他们共享一个timer,所以是这样的了,通常共享一个timer 也是用闭包写法不然,全局的话会污染的。
那么这样就是结束了,或者说debounce 是否完善了? 答案是否定的,在我们的handle 并不能获取到滚动的参数,比如说滚动的距离等,那么我们需要传递一下。
还有一个原因就是this的问题,如果不这样的话,this会变化的。
<div style="height: 200px;width: 200px;background-color: aqua;" id="test"></div>
<script>
function debounce(fn, wait) {
var timer = null;
return function () {
let context = this;
let args = arguments;
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(context, args)
}, wait);
}
}
function handle() {
console.log(this);
}
document.getElementById('test').onmousemove=debounce(handle, 1000);
</script>
这个我们得到的this是
<div style="height: 200px;width: 200px;background-color: aqua;" id="test"></div>。
如果不使用apply,那么是:Window {frames: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
如果想详细了解这一块,需要去了解闭包这一块,可以去看我的闭包这一块,或者网上搜。
function debounce(fn, wait) {
var timer = null;
return function () {
let context = this;
let args = arguments;
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(()=>{fn.apply(context,args)}, wait);
}
}
这样就ok了。
在函数防抖中,还有另外一种就是立即执行版。
立即执行的含义也是非常有意思的。还是拿这个滚动说事。
我希望在滚动的时候立马执行一个函数,但是呢,在之后继续滚动过程中,希望不要执行了。
function debounce(fn, wait) {
var timer = null;
return function () {
let context = this;
let args = arguments;
let isCall = !timer;
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = null;
}, wait);
if(isCall){
fn.apply(context,args);
}
}
}
其实是这样一个过程,如果有timer,那么干好清理timer的事,如果没有timer 那么执行需要调用的函数。
为了我们方便调用,可以结合成一个:
function debounce(fn, wait, immediate) {
var timer = null;
return function () {
let context = this;
let args = arguments;
if (timer !== null) {
clearTimeout(timer);
}
if (immediate) {
let isCall = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (isCall) {
fn.apply(context, args);
}
} else {
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
}
}
}
immediate 设置是立即执行版,还是延迟版。
函数节流
那么什么函数节流呢?假如有这样一个需求,有一个画板,现在我们有一个需求就是在画画的时候,每隔几秒,保存一次当时的画板的情况。
那么这个时候不能单纯的settimerout,因为这里的需求是画画的时候,也就是我们在画的时候。那么这个时候我们要监听到手指移动事件,
并且几秒执行一次。
function throttle(fn,wait){
var timer = null;
return function(){
let context = this;
let args = arguments;
if(!timer){
setTimeout(() => {
timer=null;
fn.apply(context,args);
}, wait);
}
}
}
每次timer=null的时候我们才去设置settimeout ,这样就好了。
当然记时方式有很多,我们也可以使用时间戳的方式。
function throttle(fn,wait){
var previous=0;
return function(){
let context = this;
let args = arguments;
var now=Date.now();
if(now-previous>wait){
fn.apply(context,args);
previous=now;
}
}
}
一般在项目中,两种一般取一个,而防抖一般都会用到,需求不一样。