Vue内防抖与节流的写法简单总结

422 阅读4分钟

防抖与节流的区别

防抖与节流达到的目的都是一段时间内只触发一次函数执行,简单阐述防抖与节流的区别:

  • 节流:在一段时间内多次触发,只执行一次,且一般是在刚开始触发时执行这一次函数。如滑动窗口时触发的操作。
  • 防抖:在一段时间内多次触发,只执行最后一次。如付款操作,多次点击只将最后一次点击提交。

4种通用的节流与防抖的方法(返回一个方法,不能直接执行)

这四种方法为网上大多数人提供的方法,这里只做汇总,可能并不适用于你的项目代码。

// js文件,可抽取进utils

// 节流函数
// 会先立即执行一次,时间戳写法
const throttle = (func, delay = 1000) => {
    //距离上一次的执行时间
    let lastTime = 0;
    //不能用箭头函数
    return function () {
      let context = this
      let args = arguments
      let now = new Date().getTime()
      //如果距离上一次执行超过了delay才能再次执行
      if(now - lastTime > delay){
        func.apply(context,args)
        lastTime = now
      }
    }
  }
// 到delay时间后才开始执行第一次
  const throttle2 = function(func, delay) {
    let timeout;
    return function() {
      let context = this;
      let args = arguments;
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null;
          func.apply(context, args)
        }, delay)
      }
    }
}

// 防抖函数
// 会先立即执行一次
const debounce = (func, delay = 1000, immediate = true) => {
    // 计时器,用来记录何时结束
    let timer = null
    //不能用箭头函数
    return function () {
      let context = this;
      let args = arguments;
      if (timer) {
        clearTimeout(timer)
      }
      // 先立即执行一次
      console.log(immediate, timer)
      if (immediate && !timer) {
        func.apply(context,args)
      }
      timer = setTimeout(() => {
        func.apply(context,args)
      }, delay)
    }
  }
// 到delay时间后才会执行
const debounce2 = function (func, delay) {
    let timer;
    return function() {
        let context = this;
        let args = arguments;
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(context, args)
        }, delay) 
    }
}

module.exports = {
    throttle,
    throttle2,
    debounce,
    debounce2
}

代码内引用

<template>
  <div>
    <el-button @click="throttlePrint">点击输出1</el-button>
    <el-button @click="throttlePrint2">点击输出2</el-button>
    <el-button @click="debouncePrint">点击输出3</el-button>
    <el-button @click="debouncePrint2">点击输出4</el-button>
  </div>
</template>

<script>
import { throttle, throttle2, debounce, debounce2 } from "@/utils/test";
export default {
  data() {
    return {};
  },
  methods: {
    print() {
      console.log("this.$test"); // 去控制台看输出时机
    },
    throttlePrint: throttle(function () {
      this.print();
    }, 2000),
    throttlePrint2: throttle2(function () {
      this.print();
    }, 2000),
    debouncePrint: debounce(function () {
      this.print();
    }, 2000),
    debouncePrint2: debounce2(function () {
      this.print();
    }, 2000),
  },
};
</script>

<style></style>

上述方法全部为返回一个function,不直接执行,下面提供直接可用的两种方法

Vue uni-app内可以直接调用的节流与防抖方法

节流:

/**
 * 节流原理:在一定时间内,只能触发一次
 * 
 * @param {Function} func 要执行的回调函数 
 * @param {Number} wait 延时的时间
 * @param {Boolean} immediate 是否立即执行
 * @return null
 */
let timer, flag;
 
function throttle(func, wait = 500, immediate = true) {
	if (immediate) {
		if (!flag) {
			flag = true;
			// 如果是立即执行,则在wait毫秒内开始时执行
			typeof func === 'function' && func();
			timer = setTimeout(() => {
				flag = false;
			}, wait);
		}
	} else {
		if (!flag) {
			flag = true
			// 如果是非立即执行,则在wait毫秒内的结束处执行
			timer = setTimeout(() => {
				flag = false
				typeof func === 'function' && func();
			}, wait);
		}
		
	}
};
export default throttle

防抖:

/**
 * 防抖原理:在一定时间内,只触发最后一次
 * 
 * @param {Function} func 要执行的回调函数 
 * @param {Number} delay 延时的时间
 * @param {Boolean} immediate 是否立即执行
 * @return null
 */
let timer;

function debounce(func, wait = 300, immediate = false) {
    if(timer){
        clearTimeout(timer);
    }
    if(immediate){
        typeof func === 'function' && func();
    }
    timer = setTimeout(() => {
        typeof func === 'function' && func();
	}, wait);
}

使用方法:

import { throttle, debounce } from '@/common/utils/xxxx';

methods:{
    certainFunctions(args){
       throttle(() => {
           console.log(args)
           // do something
       }, 1000);

       debounce(() => {
           console.log(args)
           // do something
       }, 1000)
    }
}
onLoad(){
    this.certainFunctions(args);
}

2种通用的节流与防抖的方法(可直接执行)

static _lastExecTimes = new Map()
static throttle(func, delay = 300, ...args) {
    const now = Date.now();
    const funcKey = func.toString(); // 使用函数字符串作为唯一标识

    // 如果该函数从未执行过,或者距离上次执行已超过延迟时间
    if (!this._lastExecTimes.has(funcKey)) {
      this._lastExecTimes.set(funcKey, 0); // 初始化
    }

    const lastExecTime = this._lastExecTimes.get(funcKey);

    if (now - lastExecTime >= delay) {
      func.apply(this, args);
      this._lastExecTimes.set(funcKey, now);
    }
    // 如果未达到延迟时间,则不执行任何操作
  }
static _debounceTimers = new Map()
static debounce(func, wait = 300, immediate = false, ...args) {
    const funcKey = func.toString();
    // 清理方法
    const cleanup = () => {
      if (this._debounceTimers.has(funcKey)) {
        clearTimeout(this._debounceTimers.get(funcKey));
        this._debounceTimers.delete(funcKey);
      }
    };

    cleanup(); // 先清理现有的

    if (immediate && !this._debounceTimers.has(funcKey)) {
      func.apply(this, args);
    }

    this._debounceTimers.set(
      funcKey,
      setTimeout(() => {
        if (!immediate) {
          func.apply(this, args);
        }
        cleanup();
      }, wait)
    );
  }

使用方法:

// 按钮点击事件,方法会直接执行
const btnClick = () => {
  Toolkit.throttle(() => { // Toolkit为方法所属的类
    console.error("按钮有效")
  }, 900)
}

注意事项

节流与防抖因为使用了apply改变了函数的执行上下文,也就是this指向,因此方法内要时刻留意this的指向问题,比如不能使用 箭头函数 ,因为箭头函数会默认将this改变为该函数所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。