性能优化之 防抖与节流的原理

面试中经常遇到面试官问到,手写一个防抖或者节流吧。
这道题真是说容易也容易,说不容易也不容易的题。

如果你没有自己仔细的研究过,甚至连二者的差别都说不清楚 ,就更别提手写出代码来了。


说的啥.jpg

那下面我们就一起来学习一下防抖和节流分别是什么功能,它们俩又是如何实现的呢。

节流

节流就是控制流量,在某段时间内,只执行一次。

防抖

某段时间内未结束操作,则重新计时,并在规定时间内,仅执行一次。
看起来感觉差不多,
比如浏览器的onscroll,oninput,resize,onkeyup等,如果用户一直操作滚动视窗、缩放浏览器大小,则会重新计算延时,若操作稳定了,则在约定时间后执行一次方法。


多说无益.jpg

行吧。下面先实现一个简单版本的节流:

/**
 @func 要节流的函数
 @wait 时间间隔
*/
function throttle(func, wait){
  let pre = 0;//记录上一次的时间
  return function(){
      let now = Date.now();//记录当前时间
     if(now - pre > wait){ //如果时间差大于约定的时间间隔
       func.apply(this, arguments); //则触发
      pre = now; //同时将时间更新
    }
  }
}

参考代码:Easy Throttle

上面是利用时间戳的方式实现,有一个问题存在,第一次触发会执行func,但停止触发后,不会再执行一次func。
我们试着使用定时器来优化一下,并将停止触发后要不要再执行一次作为一个参数trailing传递。

 function throttle(func, wait, options) {
  let args, context;
  let pre = 0;
  let timer;
  let later = function () {
    pre = Date.now();
    func.apply(context, args);
    args = context = null;
  };

  let throttled = function () {
    args = arguments;
    context = this;
    let now = Date.now();

    let remaining = wait - (now - pre);
    if (remaining <= 0) {
      //第一次执行,如果已经存在,则清除
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      func.apply(context, args);
      pre = now;
    } else if (!timer && options.trailing !== false) {
      //默认会触发,最后一次应该触发。
      timer = setTimeout(later, remaining);
    }
  };

  return throttled;
}

调用时:

let btn = document.getElementById("btn");
btn.addEventListener("click", throttle(logger, 1000, { trailing: true })); //1s内都算一次
function logger() {
  console.log("logger");
}

参考代码:停止触发后要不要再次执行

那么既然有限制停止触发后的再次执行,就有对于首次加载时需不需要立即执行的限制,这里我们用参数leading:false来表示一进来不要立即执行。

function throttle(func, wait, options){
    let args,context;
    let pre = 0;
    let timer;
    let later = function(){
        pre = options.leading === false ? 0:Date.now();
        func.apply(context, args);
        args = context = null;

    }
    let throttled = function (){
        args = arguments;
        context = this;
        let now = Date.now();
      
        //一进来不要立刻执行
        if(!pre && options.leading === false){
            pre = now;
        }

        let remaining = wait - (now - pre);
        if(remaining <= 0){//第一次执行
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            func.apply(context, args);
            pre = now;
        }else if(!timer && options.trailing !== false){//默认会触发,最后一次应该触发。
            timer = setTimeout(later, remaining);
        }
    }

    return throttled;
}

调用时:

let btn = document.getElementById("btn");
btn.addEventListener("click", throttle(logger, 1000, { leading: false })); 
function logger() {
  console.log("logger");
}

参考代码:是否立即执行

防抖:

function debounce(func, wait, immediate){
    let timer;
    return function(){
        clearTimeout(timer);
        if(immediate){
            let callNow =!timer; 
            if(callNow) func.apply(this, arguments);
        }
        timer = setTimeout(()=>{
            func.apply(this, arguments)
            timer = null;
        }, wait);
    }
}

调用时:

let btn2 = document.getElementById('btn2');
btn2.addEventListener('click',debounce(logger,1000, true)); //停止 时才算一次
function logger(e){
    console.log('logger2',e);
}

参考代码:debounce

好啦,笔记 就写到这里,后续可能会补充一下lodash关于二者合到一起的写法。
今天就点到为止吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。