函数防抖和函数节流

前言

这里介绍一下函数防抖和函数节流,主要用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;
		}
	}
}

一般在项目中,两种一般取一个,而防抖一般都会用到,需求不一样。

posted @ 2021-01-23 09:55  敖毛毛  阅读(161)  评论(0编辑  收藏  举报