促膝谈一谈js防抖和节流

215 阅读2分钟

防抖和节流严格来讲属于性能优化的相关内容,但实际上遇到的频率相当高,如果不处理的话势必会造成浏览器的卡掉,所以这些知识还是需要掌握的。 很多涉及到的是列表出现有滚动条得时候,有一个按钮可以回到顶部,这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现在抽象出这个功能需求,---监听浏览器滚动事件,返回当前滚动条与顶部的距离,

上代码

	function freshTop  () {
	    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
	  console.log('滚动条位置:' + scrollTop);
	}
	window.onscroll  = freshTop
	在运行得时候你会发现这个freshTop函数的默认执行频率过于频繁

函数防抖(debounce)

原理:将若干函数调用合成为一次,并在给定时间过去之后,或者连续事件完全触发完成之后,调用一次(仅仅只会调用一次)

举个例子:

滚动scroll事件,不停滑动滚轮会连续触发多次滚动事件,从而调用绑定的回调函数,我们需求是,不停止滚动时,触发一次回调,这时候可以使用函数防抖;

<style>
    * {
        padding: 0;
        margin: 0;
    }

    .box {
        width: 800px;
        height: 1200px;
    }
</style>
<body>
    <div class="container">
        <div class="box" style="background: greenyellow"></div>
        <div class="box" style="background: hotpink"></div>
        <div class="box" style="background: deeppink"></div>
        <div class="box" style="background: seagreen"></div>
    </div>
    <script>
        window.onload = function() {
           const debounce = function(fn, delay) {
                let timer = null
                return function() {
                    const context = this
                    let args = arguments
                    clearTimeout(timer) // 每次调用debounce函数都会将前一次的timer清空,确保只执行一次
                    timer = setTimeout(() => {
                        fn.apply(context, args)
                    }, delay)
                }
            }
          let num = 0
          function scrollTop() {
                num++
                console.log(`num当前是 ${num}`)
            }
            // 此处的触发时间间隔设置为500
            // document.addEventListener('scroll', debounce(scrollTop, 500))
             document.addEventListener('scroll', scrollTop)
		
			
        }
    </script>
</body>

效果如下:

防抖.png

当触发时间间隔很小,如果匀速不间断的滚动,不断触发scroll事件,用debounce函数防抖,num在一段时间里只改变了一次,直接调用scrollTop函数的效果截图,

scroll.png

浏览器在处理setTimeout 最短时间间隔是4毫秒和setInterval最短时间间隔是10毫秒当小于10毫秒的时间间隔会被调整到10毫秒,两者都有最小的时间间隔,如果未优化,scroll事件频繁触发的时间间隔也是这个最小时间间隔,可以说当我们在debounce函数中的间隔事件设置不恰当(小于这个最小时间间隔),会使debounce无效.

函数节流 throttle

当达到了一定的时间间隔就会执行一次,可以理解为减少执行频率以scroll滚动事件为例,滚动事件是及其消耗浏览器性能的,不停触发,在移动端通过scroll实现分页,不断滚动,我们不希望不断发送请求,只有当达到某个条件,比如,距离手机窗口底部200px才发送一个请求,接下来就是展示新页面的请求,一直滚动,如此反复,这个时候就得用到函数节流.

<style>
    * {
        padding: 0;
        margin: 0;
    }

    .box {
        width: 800px;
        height: 1200px;
    }
</style>
<body>
    <div class="container">
        <div class="box" style="background: greenyellow"></div>
        <div class="box" style="background: hotpink"></div>
        <div class="box" style="background: deeppink"></div>
        <div class="box" style="background: seagreen"></div>
    </div>
    <script>
        window.onload = function() {
      
	  const throttle = function(func,delay) {
			let timer = null;
			return function () {
			const context = this;  //保存事件的触发对象
			let args = arguments;
			if(!timer){            //必须确保上一次定时器执行完毕
			    timer = setTimeout(()=>{
                                                                                                                        func.call(context,...args);
			     timer=null;   
                             //及时清理,表示执行完毕,clearTimeout后timer仍有值   
                             //这是一个容易错误的地方
			  },delay)
				}
			   }
		  }
		
            let num = 0
            function scrollTop() {
                num++
                console.log(`num当前是 ${num}`)
            }
			document.addEventListener('scroll', throttle(scrollTop, 500))
			
        }
   </script>
</body>


在debounce中,在执行setTimeout函数之前总会将timer用setTimeout清除, 取消延迟代码块,确保只执行一次; 在throttle中,只要timer存在就会执行setTimeout,在setTimeout内部每次清空这个timer, 但是延迟代码块已将执行了,确保一定频率执行一次 我们依旧可以在html页面中进行测试scroll事件,运行结果如图

节流.png

实际中方法也不止一个

 方法一: 时间戳实现
			const throttle = function(fn, delay) {
			  let newTime = Date.now()
			
			  return function() {
			      const context = this
			      let arg = arguments
			      let newTime2 = Date.now()
			      if (newTime2 - newTime >= delay) {
			          fn.apply(context, arg)
			          newTime = Date.now()
			      }
			  }
			}
方法二:定时器实现
			const throttle = function(fn,delay) {
			  let timer = null
			  return function() {
				const context = this
				let arg = arguments
				if(!timer) {
				  timer = setTimeout(() => {
					fn.apply(context,arg) 
					timer = null;
				  },delay)
				}
			  }
			}

好啦,总结先到这里,大家有咩有学到一些知识呢,评论区告诉我吧💟