JavaScript性能优化之防抖和节流|小册免费学

409 阅读5分钟

防抖

当我们给一个按钮绑定点击事件,如点击提交网络请求,由于用户点击行为可能会连续触发,这样就会导致连续提交网络请求,这样会造成性能上问题,也是我们不希望出现的。这时我们就可以使用防抖函数来解决这个问题。

防抖就是在一定时间内频繁触发事件,只会执行一次,一定时间内再次触发会重新计时。

防抖函数分为非立即执行版和立即执行版。

非立即执行版本

示例代码如下:

// func是需要防抖执行的函数
// wait是防抖时间
const debounce = (func, wait = 300) => {
  // 缓存一个定时器
  let timer;
  // 返回的函数是每次用户实际调用的防抖函数
  // 这里形成了一个闭包
  return function() {
    // 保证this指向不变和正常获取参数
    let context = this;
    let args = arguments;
    // 如果已经设定过定时器了就清空上一次的定时器,重新计时
    if (timer) clearTimeout(timer);
    // 设定一个新的定时器,延迟执行用户传入的方法
    timer = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  }
}

这样对于上面的点击事件提交网络请求,在300ms内多次点击则只会提交一次网络请求。

存在的问题

效果演示代码:

const debounce = (func, wait = 300) => {
  let timer;
  return function() {
    console.log('点击了');
    let context = this;
    let args = arguments;
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  }
}
document.querySelector('body').onclick = debounce(function(){
    console.log('执行了');
}, 1000);

演示结果:

image.png

上面的代码演示了在1000ms之内多次触发点击事件,只会在最后一次触发后1000ms之后才会执行目标方法。这样就存在一个两个问题:

  • 如果用户一直点击,则永远也不会执行目标方法;
  • 如果用户只点击了一次,则这次会在1000ms之后执行目标方法;

立即执行版本

要解决上面非立即执行版本的两个问题,就需要使用立即执行版本的防抖函数。

示例代码如下:

// func是需要防抖执行的函数
// wait是防抖时间
const debounce = (func, wait = 300) => {
  // 缓存一个定时器
  let timer;
  // 返回的函数是每次用户实际调用的防抖函数
  // 这里形成了一个闭包
  return function() {
    // 保证this指向不变和正常获取参数
    let context = this;
    let args = arguments;
    // 如果已经设定过定时器了就清空上一次的定时器,重新计时
    if (timer) clearTimeout(timer);
    // 判断是否是第一次
    let callNow = !timer;
    // 设定一个新的定时器,延迟执行用户传入的方法
    timer = setTimeout(() => {
        timer = null;
    }, wait);
    如果是第一次则立即执行
    if (callNow) func.apply(context, args);
  }
}

效果演示代码:

const debounce = (func, wait = 300) => {
  let timer;
  return function() {
    console.log('点击了');
    let context = this;
    let args = arguments;
    if (timer) clearTimeout(timer);
    let callNow = !timer;
    timer = setTimeout(() => {
        timer = null;
    }, wait);
    if (callNow) func.apply(context, args);
  }
}
document.querySelector('body').onclick = debounce(function(){
    console.log('执行了');
}, 1000);

演示结果:

image.png

这样立即执行版本就解决了上面的两个问题。

节流

当我们给一个滚动或者鼠标滑动这类触发频率非常高的行为绑定事件,如滚动事件中会发起网络请求,但是这样在滚动过程中一直发起请求,这样会造成性能上的问题,也不是我们想要的结果。这时就可以使用节流函数来处理。

节流指连续触发的事件在 n 秒中只执行一次的函数。

节流也有两种实现方式,分别是时间戳版和定时器版。

时间戳版

示例代码:

// func是需要节流执行的函数
// wait是节流时间
const throttle = (func, wait = 300) => {
  // 上一次执行节流函数的时间
  let lastTime = 0;
  // 返回的函数是每次用户实际调用的节流函数
  // 这里形成了一个闭包
  return function() {
    // 保证this指向不变和正常获取参数
    let context = this;
    let args = arguments;
    // 当前时间
    let now = +new Date();
    // 将当前时间和上一次执行节流函数时间比较
    // 如果差值大于设置的节流时间就执行目标函数
    if (now - lastTime > wait) {
      并将当前时间记为上一次执行的时间
      lastTime = now;
      func.apply(context, args);
    }
  }
}

效果演示代码:

const throttle = (func, wait = 300) => {
  let lastTime = 0;
  return function() {
    console.log('鼠标移动了');
    let context = this;
    let args = arguments;
    let now = +new Date();
    if (now - lastTime > wait) {
      lastTime = now;
      func.apply(context, args);
    }
  }
}
document.querySelector('body').onmousemove = throttle(function(){
    console.log('执行了');
}, 500);

演示结果:

image.png

特点:持续事件触发后会立即执行一次,然后按节流时间间隔执行。

定时器版

示例代码:

// func是需要节流执行的函数
// wait是节流时间
const throttle = (func, wait = 300) => {
  // 缓存一个定时器
  let timer;
  // 返回的函数是每次用户实际调用的节流函数
  // 这里形成了一个闭包
  return function() {
    // 保证this指向不变和正常获取参数
    let context = this;
    let args = arguments;
    // 如果定时器结束或未设定过就设定定时器计时
    if (!timer) {
      timer = setTimeout(() => {
        // 设定节流时间后清除定时器,执行目标方法
        timer = null;
        func.apply(context, args);
      }, wait);
    }
  }
}

效果演示代码:

const throttle = (func, wait = 300) => {
  let timer;
  return function() {
    console.log('鼠标移动了');
    let context = this;
    let args = arguments;
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        func.apply(context, args);
      }, wait);
    }
  }
}
document.querySelector('body').onmousemove = throttle(function(){
    console.log('执行了');
}, 500);

演示结果:

image.png

特点:持续事件触发后不会立即执行,然后按节流时间间隔执行,最后结束时候会执行一次。

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情