yorickshan
1/8/2019 - 2:47 AM

节流与防抖

函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段。 大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片。 由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的。反而形成一种视觉效果,就是一张图。就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样。 同理,可以类推到js代码。在一定时间内,代码执行的次数不一定要非常多。达到一定频率就足够了。因为跑得越多,带来的效果也是一样。 倒不如,把js代码的执行次数控制在合理的范围。既能节省浏览器CPU资源,又能让页面浏览更加顺畅,不会因为js的执行而发生卡顿。这就是函数节流和函数防抖要做的事。

函数节流是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。 函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。

一般我们在 input 输入框中输出值,并且向服务端查询数据的时候,如果每次 input 值发生改变,都向服务器发请求的话,难免会资源浪费,并且用户体验并不好,那么我们可以在 input 输入值后延迟一段时间,再发请求。就是 防抖

<body>
  <input type="text" id="text">
  <script>
    document.getElementById('text').oninput = function () {
      search()
    }

    function search () {
      console.log('发起请求')
    }
  </script>
</body>

上面代码,我们可以得到, 每次 input 的值发生改变,都会输出 发起请求, 为了调整发起请求的频率,我将代码调整这样

<body>
  <input type="text" id="text">
  <script>
    const text = document.getElementById('text')
    text.oninput = debounce(search, 2000)

    function search() {
      console.log('发起请求')
    }

    function debounce(fn, delay) {
      let timer
      return function (...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          fn.apply(this, args)
        }, delay)
      }
    }
  </script>
</body>
//当 input 输入一个值的时候,间隔 2000ms 才输出一个 发起请求, 如果我们连续输入, 只会输出一个 发起请求

搜索引擎边输入边联想是怎么实现的?

self.bindEventAboutGrid($("#SE1G040_confirm_button"), $.proxy(throttle(this.confirm, 1000), this), dm, true);

函数节流的基本思想是设置一个定时器,在指定时间间隔内运行代码时清楚上一次的定时器,并设置另一个定时器,知道函数请求停止并超过时间间隔才会执行

//节流函数,控制事件在一定时间内只发生一次, 防止重复请求, 影响性能

var throttle = function (fn, delay) { //参数fn为要执行的函数, delay为时长
    var timer = null;
    return function () { //通过闭包来实现。首先在函数内定义一个变量timer,然后返回一个函数,我们知道由于在返回函数中包含对timer的引用,因此timer并不会立即被清除。(我们也可以将timer定义为全局变量)
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn();
        }, delay);
    }
};

function throttle(method,delay){
    var timer=null;
    return function(){
        var context=this, args=arguments;
        clearTimeout(timer);
        timer=setTimeout(function(){
            method.apply(context,args);
        },delay);
    }
}

function throttle(method, context){ //参数method为要执行的函数, context为执行环境,如果执行环境未定义,默认则为windows
    clearTimeout(method.timer);
    method.timer=setTimeout(function(){ //将定时器设置为函数的一个属性
        method.call(context);
    }, 300)
}

以上两种方案存在一个问题,即如果事件一直触发,那么函数将永远不会被执行,这在某些时候并不符合我们的需求,可能我们只是想在规定时间内减少函数执行次数,所以对以上函数做如下改进:

function scrollFn(){
    console.log(1)
}
function throttle(method,delay,duration){
    var timer=null;
    var begin=new Date();    
    return function(){                
        var context=this, args=arguments;
        var current=new Date();        
        clearTimeout(timer);
        if(current-begin>=duration){
            method.apply(context,args);
            begin=current;
        }else{
            timer=setTimeout(function(){
                method.apply(context,args);
            },delay);
        }
    }
}
window.onscroll=throttle(scrollFn,100,500)

在初始的时候定义一个begin开始时间,当时间间隔超过duration时,则执行一次函数,这样做到了不重复调用,又能保证500秒执行一次