防抖
在日常开发中,经常会有一个按钮防止二次点击操作的需求。比如支付订单,创建商品,点赞收藏之类的。虽然不知道用户为什么要在0.5秒内连续点击两次甚至三次
 
这些需求都可以通过函数防抖来实现。
 
函数防抖简单来说就是,触发高频事件后n秒内只执行一次,如果在时间内再次触发,那重新计算时间


function debounce(fn) {

  let timer = null

  return function() {

    clearTimeout(timer)

    timer = setTimeout(() => {

      fn.apply(this, arguments)

    }, 1000)

  }

}

 

function sayHi(params) {

  console.log('Hi');

}

  

let ObjDIV = document.getElementById('div')

ObjDIV.addEventListener('click', debounce(sayHi))
上面的代码在1s内连续点击的话,只会触发一次 sayHi ,而且1s的等待时间会从最后一次点击重置。
 
但是上面的代码有个缺陷, sayHi 只能在等待时间结束后调用,实际上一些需求是不可能这样做的。比如点赞,不可能要等1s之后才有反馈。所以我们将防抖函数进行下优化。
 
function debounce(func, wait, immediate) {

  let timeout, args, context, timestamp, result

 

  const later = function() {

    // 据上一次触发时间间隔

    const last = +new Date() - timestamp

 

    // 上次被包装函数被调用时间间隔last小于设定时间间隔wait

    if (last < wait && last > 0) {

      timeout = setTimeout(later, wait - last)

    } else {

      timeout = null

      // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用

      if (!immediate) {

        result = func.apply(context, args)

        if (!timeout) context = args = null

      }

    }

  }

 

  return function(...args) {

    context = this

    timestamp = +new Date()

    const callNow = immediate && !timeout

    // 如果延时不存在,重新设定延时

    if (!timeout) timeout = setTimeout(later, wait)

    if (callNow) {

      result = func.apply(context, args)

      context = args = null

    }

 

    return result

  }

}
这样函数就会立即执行,并且在接下来的 wait 里不会重复执行,直到 wait 时间之后再次点击才可以执行下一次。
 
不带立即执行的应用场景
 
在搜索引擎搜索问题的时候,我们希望用户输入完最后一个字才调用查询接口,而不是在输入每个字都调用接口。当然这个可以使用blur或者按钮事件触发,具体使用哪种方法还是根据需求来定。
节流
在这个 wait 时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
 
节流的一些应用场景:
 
mousemove的拖动
resize事件的触发
scroll的事件监听
/**

 * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait

 *

 * @param  {function}   func      回调函数

 * @param  {number}     wait      表示时间窗口的间隔

 * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。

 *                                如果想忽略结尾函数的调用,传入{trailing: false}

 *                                两者不能共存,否则函数不能执行

 * @return {function}             返回客户调用函数

 */

_.throttle = function(func, wait, options) {

    var context, args, result;

    var timeout = null;

    // 之前的时间戳

    var previous = 0;

    // 如果 options 没传则设为空对象

    if (!options) options = {};

    // 定时器回调函数

    var later = function() {

      // 如果设置了 leading,就将 previous 设为 0

      // 用于下面函数的第一个 if 判断

      previous = options.leading === false ? 0 : _.now();

      // 置空一是为了防止内存泄漏,二是为了下面的定时器判断

      timeout = null;

      result = func.apply(context, args);

      if (!timeout) context = args = null;

    };

    return function() {

      // 获得当前时间戳

      var now = _.now();

      // 首次进入前者肯定为 true

  // 如果需要第一次不执行函数

  // 就将上次时间戳设为当前的

      // 这样在接下来计算 remaining 的值时会大于0

      if (!previous && options.leading === false) previous = now;

      // 计算剩余时间

      var remaining = wait - (now - previous);

      context = this;

      args = arguments;

      // 如果当前调用已经大于上次调用时间 + wait

      // 或者用户手动调了时间

    // 如果设置了 trailing,只会进入这个条件

  // 如果没有设置 leading,那么第一次会进入这个条件

  // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了

  // 其实还是会进入的,因为定时器的延时

  // 并不是准确的时间,很可能你设置了2秒

  // 但是他需要2.2秒才触发,这时候就会进入这个条件

      if (remaining <= 0 || remaining > wait) {

        // 如果存在定时器就清理掉否则会调用二次回调

        if (timeout) {

          clearTimeout(timeout);

          timeout = null;

        }

        previous = now;

        result = func.apply(context, args);

        if (!timeout) context = args = null;

      } else if (!timeout && options.trailing !== false) {

        // 判断是否设置了定时器和 trailing

    // 没有的话就开启一个定时器

        // 并且不能不能同时设置 leading 和 trailing

        timeout = setTimeout(later, remaining);

      }

      return result;

    };

  };
在vue里怎么写?
 
import { debounce } from "@/utils/index";

 

onCreateSubmit: debounce(

  function() {

    createIntegralGoods(this.form).then(res => {

      this.$message.success("商品创建成功");

      this.$router.push({

        path: "/campaign/active/integral-mall/index"

      });

    });

  },

  1000,

  true
————————————————