Skip to content

一、防抖和节流

防抖和节流其实都是在规避频繁触发回调导致大量计算,从而影响页面发生抖动甚至卡顿。简单的说将多次回调比如页面点击或 ajax 调用变为一次。防抖和节流的区别在于以第一次为准还是最后一次为准。

二、常见的使用场景

三、节流 Throttle - 调用多次,只有第一次有效

基本思路:当一个事件被触发时,设置一个标志位,表示当前正在执行的回调函数,如果在执行过程中该事件又被触发,则忽略该次触发,直接回调函数执行完毕,在重置标志位。这样可以保证在一段时间内,只执行一次回调函数。节流的作用是限制事件的执行频率,比如滚动加载,拖拽等。
代码示例:

js
// 节流函数
function throttle(fn, delay) {
    // 定义一个标志位
    let flag = true;
    // 返回一个新的函数
    return function(…args) {
        // 如果标志位为false,则直接返回
        if (!flag) {
            return;
        }
        // 设置标志位为false
        flag = false;
        // 设置一个延时函数
        setTimeout(() => {
            // 调用原始函数,并绑定this和参数
            fn.apply(this, args);
            // 重置标志位为true
            flag = true;
        }, delay);
    };
}

四、防抖 Debounce - 最后一次有效

基本思路:当一个事件被触发时,设置一个延时函数,如果在延时时间内,该事件又被触发,则取消之前的延时函数,重新设置一个新的延时函数,这样可以保证只有在事件停止触发一段时间后,才执行真正的回调函数。防抖的作用是避免频繁的执行一些消耗性能的操作,比如输入框搜索,窗口大小调整等。
代码示例:

js
// 防抖函数
function debounce(fn, delay) {
    // 定义一个定时器
    let timer = null;
    // 返回一个新的函数
    return function (...args) {
        // 如果已经有定时器,则取消
        if (timer) {
            clearTimeout(timer);
        }
        // 设置一个新的定时器
        timer = setTimeout(() => {
            // 调用原始函数,并绑定this和参数
            fn.apply(this, args);
        }, delay);
    };
}
export const debounce = (fn, delay) => {
    let timer = null;
    return function (...args) {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
};

五、在 vue 中使用

记一个疑问,总以为使用的时候写箭头函数和封装方法也写箭头函数有关系,封装方法时候,写箭头函数和 function 都可以,只要使用的时候,按照下方使用就行

vue
<script setup>
import { debounce } from "文件路径";
const inputChange = debounce(() => {
  // 业务逻辑
}, 2000);
</script>

六、另一种写法

js
let timer = null;
export function debounce(fn, delay = 1000) {
    if (timer != null) {
        clearTimeout(timer);
        timer = null;
    }
    timer = setTimeout(fn, delay);
}

let timer = null;
export function throttle(fn, delay = 300) {
    if (timer == null) {
        timer = setTimeout(() => {
            fn();
            clearTimeout(timer);
            timer = null;
        }, delay);
    }
}
vue
<script setup>
import { debounce, throttle } from "文件路径";
const inputChange = () =>
  debounce(() => {
    // 业务逻辑
  }, 2000);

const inputChange = () =>
  throttle(() => {
    // 业务逻辑
  }, 1000);
</script>

七、新的封装,使用vue3中的customRef来实现封装

customRef:创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
customRef()预期接收一个工厂函数,这个工厂函数接收track和trigger两个函数作为参数,并返回一个带有get()和set()方法的对象。
正常情况下,在get()里面调用track(),在set()中调用trigger()。

js
import { customRef, ref } from “vue”;
// 定义一个防抖函数
// data为初始化的的值
function debounceRef(data, delay = 300) {
    // 创建一个定时器
    let timer = null;
    // 返回一个自定义的ref
    // 对 delay 进行判断,如果传递的是 null 则不需要使用 防抖方案,直接返回使用 ref 创建的。
    return delay == null ? ref(data) : customRef((track, trigger) => {
        return {
            get() {
                // 收集依赖
                track();
                // 返回当前数据
                return data;
            },
            set(value) {
                // 清除定时器
                if (timer) {
                    clearTimeout(timer);
                    timer = null
                }
                // 设置一个新的定时器
                timer = setTimeout(() => {
                    // 更新数据
                    data = value;
                    // 触发更新
                    trigger();
                }, delay);
            },
        };
    });
}

// 节流函数
// data 为创建时的数据
// delay 为节流时间
function throttleRef (data, delay = 300){
    // 创建定时器
    let timer = null;
    // 对 delay 进行判断,如果传递的是 null 则不需要使用 节流方案,直接返回使用 ref 创建的。
    return delay == null
        ?
        // 返回 ref 创建的
        ref(data)
        :
        // customRef 中会返回两个函数参数。一个是:track 在获取数据时收集依赖的;一个是:trigger 在修改数据时进行通知派发更新的。
        customRef((track, trigger) => {
            return {
                get () {
                    // 收集依赖
                    track()
                    // 返回当前数据的值
                    return data
                },
                set (value) {
                    // 判断
                    if(timer == null){
                        // 创建定时器
                        timer = setTimeout(() => {
                            // 修改数据
                            data = value;
                            // 派发更新
                            trigger()
                            // 清除定时器
                            clearTimeout(timer)
                            timer = null
                        }, delay)
                    }
                }
            }
        })
}
vue
<script setup>
// 创建
const count = debounceRef(0, 300)

// 函数中使用
const addCount = () => {
  count.value += 1
}
// v-model 中使用
<input type="text" v-model="count">

Released under the MIT License.