JavaScript-函数节流和防抖

175 阅读3分钟

如果只需要代码,就直接拿取最底部的一段代码即可

当我们使用window.resize mousemove等事件的时候,会非常频繁的触发,对于的函数,如果还要操作dom元素的,将会十分的消耗性能的,这时候就需要节流,防抖来解决。

函数防抖(debounce)

这里用一个例子,来进行讲解

假设我在一个输入框里输入内容,他会自动的根据输入框的内容给我显示搜索的结果。 但是这里的要求是在输入完成后,等待一段时间再触发搜索的功能,并且在这段时间还没到达的时候,如果在次进行输入,那么就要重新开始计时。

不进行处理的显示

//html
      <div class="container">
            <input id="input" type="text">
            <div id="content">
            </div>
        </div>
//js
    //假设这是请求函数
    function getData(value) {
      console.log("runs");
      return `搜索${value}的内容`;
    }
    const input = document.getElementById("input");
    const content = document.getElementById("content");
    input.oninput = function (e) {
      content.innerText = getData(input.value);
    };


显示结果。这里可以看到每进行一次输入就会触发,请求函数,如果是真实的api接口,就过度浪费 uTools_1687340132918.png

基础版防抖函数

这里是通过全局变量来存储setTimeout的id,再每次触发函数的时候,就请求之前的setTimeout(也可以用if来判断是否要清除),然后再开启一个新的setTimeout来执行函数

let debounce_timer;
/**
 * 防抖函数
 * @param {*} callback 需要运行的函数
 * @param {*} duration 需要等待的时间
 */
function debounce(callback, duration) {
  clearTimeout(debounce_timer);
  debounce_timer = setTimeout(function () {
    callback();
    debounce_timer = null;
  }, duration);
}

//请求函数
function getData(value) {
  console.log("runs");
  return `搜索${value}的内容`;
}

const input = document.getElementById("input");
const content = document.getElementById("content");
input.oninput = function (e) {
  debounce(function () {
    content.innerText = getData(input.value);
  }, 1000);
};

最终结果,这里已经基本符合我们的要求了,但是采用这种方法也会带来一个问题就是全部变量的污染,所以接下来我们继续优化节流函数

uTools_1687344029329.png

进阶版防抖函数

首先,讲解一下高阶函数的定义,就是当一个函数的返回值是一个函数的话,那么它就是高阶函数

之前的节流函数的问题是会污染全局变量,于是我们就直接将,debounce_time变量,放在debounce函数内部,将功能函数给return出去。这里要注意被return出去的函数,再被调用的时候,依旧访问的是他debounce函数作用域里面的debounce_time变量。(这里需要学习一下,闭包与作用域的知识,我也会逐步更新的),所以每次清空的依旧是同一个debounce_time变量。采用了一个很巧妙的方法来解决的。

/**
 * 防抖函数
 * @param {*} callback 需要运行的函数
 * @param {*} duration 需要等待的时间
 * @returns 返回一个函数
 */
function debounce(callback, duration) {
  var debounce_timer;
  return function () {
    clearTimeout(debounce_timer);
    debounce_timer = setTimeout(function () {
      callback();
      debounce_timer = null;
    }, duration);
  };
}

//请求函数
function getData(value) {
  console.log("runs");
  return `搜索${value}的内容`;
}

const input = document.getElementById("input");
const content = document.getElementById("content");
// 这里不能放在input事件里面,否则返回的函数,就不是同一个作用域里面的了
let handleSearch = debounce(function () {
  content.innerText = getData(input.value);
}, 1000);
input.oninput = function (e) {
  handleSearch();
};

来看执行结果,依旧没有问题

uTools_1687345511660.png 但是这里还有最后一个问题,就是传参

再这段代码中,我们不处理debounce函数,假设我需要一些参数,来完善处理函数,直接传递是拿不到的

let handleSearch = debounce(function (res) {
  console.log("我需要参数来处理,该函数,参数是" + res);
  content.innerText = getData(input.value);
}, 1000);
input.oninput = function (e) {
  handleSearch("参数1", "参数2");
};

uTools_1687347077815.png

我们继续完善debounce函数,拿不到参数是因为,给handleSearch传递的参数,最终是debounce函数中,return 的那个函数拿到的,而res的形参,需要debounce函数中的callback函数,传参才可以的

所以这里我们首先通过 debounce函数的 return 函数,的argument拿到传递的参数,(用arguments获取的原因,如果传递多个参数,可以直接用arguments获取到,而不用写多个形参),再讲arguments的值传递给callback函数,这里用apply的原因,apply的第二个参数是传递实参,并且是以数组的格式。

/**
 * 防抖函数
 * @param {*} callback 需要运行的函数
 * @param {*} duration 需要等待的时间
 * @returns 返回一个函数
 */
function debounce(callback, duration) {
  var debounce_timer;
  return function () {
    let args = arguments;
    clearTimeout(debounce_timer);
    debounce_timer = setTimeout(function () {
      callback.apply(null, args);
      debounce_timer = null;
    }, duration);
  };
}

//请求函数
function getData(value) {
  console.log("runs");
  return `搜索${value}的内容`;
}

const input = document.getElementById("input");
const content = document.getElementById("content");
// 这里不能放在input事件里面,否则返回的函数,就不是同一个作用域里面的了
let handleSearch = debounce(function (res) {
  console.log("我需要参数来处理,该函数,参数是" + res);
  content.innerText = getData(input.value);
}, 1000);
input.oninput = function (e) {
  handleSearch("参数1", "参数2");
};

这里看结果,最终拿到参数了的,这里是用的arguments来拿取,也可以通过解构的方法实现,更为简单

function debounce(callback, duration) {
  var debounce_timer;
  return function (...args) {
    if (debounce_timer) {
      clearTimeout(debounce_timer);
    }
    debounce_timer = setTimeout(function () {
      callback(...args);
    }, duration);
  };
}

uTools_1687347576167.png

函数节流

函数节流是在固定的时间里执行一次

下面是一个输入框,我会不断地进行输入,但是我需要每过1s,才打印输入的内容

用时间戳实现函数节流

// 首先获取到input,并且给他绑定键盘点击事件,然后,打印输入的内容
const input = document.getElementById("input");
let res = throttle(() => {
  console.log(input.value);
}, 2000);
input.onkeydown = function () {
  res();
};

//节流函数

function throttle(callback, duration) {
  let defaultTime = 0; //定义最开始默认的时间
  return function (...args) {
    var nowTime = new Date(); //获取当前时间戳
    if (nowTime - defaultTime > duration) {
      // 如果当前时间戳,前去之前的时间大于我们传递的等待时间,就可以执行代码
      callback(...args);
      defaultTime = nowTime;
    }
  };
}

uTools_1687514482802.png

计时器实现

function throttle(callback, duration) {
  let timer; //用于记录计时器
  return function (...args) {
    if (!timer) {
    //如果计时器为空,代表duration时间到了,可以执行
      callback(...args);
      timer = setTimeout(() => {
        timer = null;//当duration时间到达,清空timer
      }, duration);
    }
  };
}