防抖及节流详细解析,并在vue中使用防抖和节流

什么是防抖什么是节流?

  • 函数防抖(debounce)

函数防抖,就是指触发事件后,在 n 秒后只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数的执行时间。
简单的说,当一个动作连续触发,只执行最后一次。

  • 函数节流(throttle)

限制一个函数在一定时间内只能执行一次

debounce和throttling 各有特点,在不同的场景要根据需求合理的选择策略。
如果事件触发是高频但是有停顿时,可以选择debounce; 
在事件连续不断高频触发时,只能选择throttling,因为debounce可能会导致动作只被执行一次,界面出现跳跃。
  • 正常、防抖、节流示意图
    chu

  • 常见的应用场景

函数防抖(debounce)的应用场景

连续的事件,只需触发一次的回调场景有:

  1. 搜索框搜索输入。
  2. 只需要用户最后一次输入完再发送请求 手机号、邮箱格式的输入验证检测
  3. 窗口大小的resize 。只需窗口调整完成后,计算窗口的大小,防止重复渲染

函数节流(throttle)的应用场景

间隔一段时间执行一次回调的场景有:

  1. 滚动加载,加载更多或滚动到底部监听
  2. 谷歌搜索框,搜索联想功能
  3. 高频点击提交,表单重复提交
  4. 省市信息对应字母快速选择

防抖和节流是针对响应跟不上触发频率这类问题的两种解决方案。

 在给DOM绑定事件时,有些事件我们是无法控制触发频率的。 
 如鼠标移动事件onmousemove, 滚动滚动条事件onscroll,窗口大小改变事件onresize,瞬间的操作都会导致这些事件会被高频触发。 
 如果事件的回调函数较为复杂,就会导致响应跟不上触发,出现页面卡顿,假死现象。 
 在实时检查输入时,如果我们绑定onkeyup事件发请求去服务端检查,用户输入过程中,事件的触发频率也会很高,会导致大量的请求发出,响应速度会大大跟不上触发。
 针对此类快速连续触发和不可控的高频触发问题,debounce 和 throttling 给出了两种解决策略;

debounce,去除抖动。

特点:

  1. 当事件快速连续不断触发时,动作只会执行一次。
  2. 延迟debounce,是在周期结束时执行。
  3. 前缘debounce,是在周期开始时执行。
  4. 当触发有间段,且间段大于我们设定的时间间隔,动作就会有多次触发。

延迟debounce,示意图:
在这里插入图片描述

    策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。

前缘debounce, 示意图:
在这里插入图片描述

   后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。

区别:

  1. 例如在搜索引擎搜索问题时,希望用户输入完最后一个字才调用查询接口,这个时候就用延迟调用的函数,在一连串(间隔小于wait的)函数触发后调用。
  2. 用户点击收藏的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变收藏按钮的样子,用户就立马可以得到反馈。这个情况适用立即执行的防抖函数,在总是在第一次调用,且下一次调用前必须与前一次调用的时间间隔大于wait才会触发。debounce的特点是当事件快速连续不断触发时,动作只会执行一次。延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。

延迟版:

export const debounce = (fn, awaits) => {
    let timer;
    return function () {
        console.log("进入防抖");
        let context = this;
        let args = arguments;
        if (timer) {
            clearTimeout(timer);
            timer = null;
        } else {
            timer = setTimeout(function () {
                fn.apply(context, args);
            }, awaits);
        }
    };
};

立即执行版

export const nowDebounce = (func, wait) => {
    let timeout;
    return function () {
        console.log("进入防抖");
        const context = this;
        const args = [...arguments];
        if (timeout) clearTimeout(timeout);
        const callNow = !timeout;
        timeout = setTimeout(() => {
            timeout = null;
        }, wait)
        if (callNow) func.apply(context, args)
    }
} 

两者结合版

export const boDebounce = (func, wait, immediate) => {
    console.log("进入防抖");
    let timeout;
    return function () {
        const context = this;
        const args = [...arguments];
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            const callNow = !timeout;
            timeout = setTimeout(() => {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(() => {
                func.apply(context, args)
            }, wait);
        }
    }
}

实例使用

<template>
  <div>
    <el-input v-model="input" @input="getSearch"> </el-input>
    <el-input v-model="nowInput" @input="getNowSearch"> </el-input>
  </div>
</template>
<script>
import { debounce, nowDebounce } from "@/store/debounce.js";
export default {
  data() {
    return {
      input: "",
      nowInput: "",
    };
  },
  methods: {
      getSearch: boDebounce(
      function () {
        console.log("拿到数据");
      },
      1000,
      false
    ),
    getSearch: debounce(function () {
      console.log("拿到数据");
    }, 1000),
    getNowSearch: nowDebounce(function () {
      console.log("立即拿到数据");
    }, 1000),
  },
};
</script>

总结:

  1. 对于按钮防点击来说的实现:如果函数是立即执行的,就立即执行调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行,一旦开始一个定时器,只要定时器还存在,每一次点击都会重新计时,定时器时间到,重置为null,就可以再次点击了。
  2. 对于延时执行的函数来说的实现:清除定时器id,如果是延时调用就调用函数。

throttling 节流

策略是,固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 
节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。

延迟throttling示意图:
在这里插入图片描述

前缘throttling 示意图:
在这里插入图片描述

throttling的特点在连续高频触发事件时,动作会被定期执行,响应平滑。

时间戳版,立即执行版

export const nowThrottle = (func, wait) => {
    console.log("进入节流")
    var previous = 0;
    return function () {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

计时器版,延迟执行版

export const throttle = (func, wait) => {
    console.log("进入节流")
    let timeout;
    return function () {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }
    }
}

两者结合版

export const boThrottle = (func, wait, type) => {
    console.log("进入节流")
    let previous;
    let timeout
    if (type === "now") {
        previous = 0;
    } else if (type === "late") {
        timeout;
    }
    return function () {
        let context = this;
        let args = arguments;
        if (type === "now") {
            let now = Date.now();
            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        } else if (type === "late") {
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }
    }
}

实例使用

<template>
  <div>
    <div class="mouseDiv" @mousemove="mouseNoMoveEvent">{{ mouseNowNum }}</div>
    <div class="mouseDiv" @mousemove="mouseMoveEvent">{{ mouseNum }}</div>
  </div>
</template>
<script>
import {
  throttle,
  nowThrottle,
  boThrottle
} from "@/store/debounce.js";
export default {
  data() {
    return {
      mouseNowNum: 0,
      mouseNum: 0,
    };
  },
  methods: {
    mouseNoMoveEvent: nowThrottle(function () {
      this.mouseNowNum++;
      console.log("立即执行");
    }, 1000),
    mouseMoveEvent: throttle(function () {
      this.mouseNum++;
      console.log("延迟执行");
    }, 1000),
    
    
     mouseNoMoveEvent: boThrottle(
      function () {
        this.mouseNowNum++;
        console.log("立即执行");
      },
      1000,
      "now"
    ),
    mouseMoveEvent: boThrottle(
      function () {
        this.mouseNum++;
        console.log("延迟执行");
      },
      1000,
      "late"
    ),
  },
};
</script>
<style>
.mouseDiv {
  margin: 20px auto;
  width: 1000px;
  height: 400px;
  line-height: 400px;
  text-align: center;
  font-size: 30px;
  background-color: lightblue;
}
</style>
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值