聊聊节流防抖

326 阅读6分钟

防抖(debounce)

含义

当某函数被高频持续触发时,只在一段时间内无触发,函数才会执行。如按钮点击更新,多次点击按钮会触发对应的更新函数,函数在高频点击过程中不会执行,只有最后一次点击,且规定的时间段内未再次点击时,更新函数触发,执行更新操作。

应用场景:

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

代码

处理方式:利用定时器,设置延迟时间,延迟时间内未再次触发,才执行函数,保证在单位时间内在最后的时刻触发。

function debounce(func, wait) {
    let timeout;
    return function () {
        let context = this; //保存this指向
        let args = arguments; 
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(function () {
            func.apply(context, args)
        }, wait);
    }
}

注:用let context = this;保存this指向的原因是,在 setTimeout 中的回调函数的 this 默认指向全局对象 window,如果是严格模式下则为 undefined。 如果setTimeout中是箭头函数,而箭头函数与普通函数不同,它们不会创建自己的 this,而是捕获词法环境中的 this 值,也就是说,它们继承了定义它们的位置的 this 值。 箭头函数被定义在返回的匿名函数内部,因此它继承了该匿名函数的 this,而该匿名函数的 this 由调用它的上下文决定。故上述代码等价于:

function debounce(func, wait) {
    let timeout;
    return function () {
        let args = arguments; 
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(() => {
            func.apply(this, args)
        }, wait);
    }
}

节流(throttle)

含义

一段时间内函数持续,频繁触发。在单位时间内只执行一次函数。节流会稀释函数的执行频率。

应用场景:

  1. 高频事件 例如:快速点击、鼠标滑动、resize,scroll事件(每触发或者滚动1像素就执行),拖拽时候的mousemove。
  2. 滚动加载,加载更多或滚到底部监听
  3. 搜索框,搜索联想功能

代码

处理方式:利用定时器,等定时器执行完毕,才开启定时器,保证单位时间内执行一次。重点在于单位时间内能执行一次触发,保证触发一次即可稀释函数的执行频率。

   function throttle(fn, delay = 500) {
    let timer = null
    return function (...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args)timer = null
            }, delay);
        }
    }
}

vue项目中应用

使用节流防抖函数

应用场景:搜索输入优化
在实时搜索(如搜索框自动完成功能)中,我们希望只在用户停止输入后才发起搜索请求,以减少不必要的服务器负载和网络请求。

<label for="search-input">Name:</label>
    <input type="text" id="search-input" name="name">
    <script>
        function debounce(func, delay) {
            let timeout;
            return function (...args) {
                if (timeout) clearTimeout(timeout);
                timeout = setTimeout(() => {
                    func.apply(this, args);
                }, delay);
            };
        }

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00dc055671de4ba98f1997c44f3dc3a1~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=638&h=64&s=4202&e=png&b=ffffff)
        function handleSearchInput(event) {
            console.log("Fetching data for:", event.target.value);
            // 实际的搜索请求逻辑
        }

        const debouncedHandleSearchInput = debounce(handleSearchInput, 1000);

        document.getElementById('search-input').addEventListener('input', debouncedHandleSearchInput);
    </script>

image.png

应用场景:窗口调整大小事件优化
在调整浏览器窗口大小时,resize 事件可以非常频繁地触发,这可能导致性能问题。使用节流可以限制这个函数的执行频率。

function throttle(func, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

function handleResize() {
    console.log('Window size:', window.innerWidth, 'x', window.innerHeight);
}

const throttledHandleResize = throttle(handleResize, 200);

window.addEventListener('resize', throttledHandleResize);

使用lodash库函数

还可以直接使用lodash的库函数实现节流防抖 在实现搜索功能时,通常希望在用户停止输入一段时间后再进行查询,以减少不必要的服务器请求。以下是一个使用 lodashdebounce 方法来实现这一功能的示例:

import _ from 'lodash';

// 假设这是一个搜索函数,它会根据用户的输入进行数据查询
function search(query) {
  console.log('Searching for:', query);
  // 实际的查询逻辑可以是发送一个API请求到服务器
}

// 使用lodash的debounce方法来包装search函数
const debouncedSearch = _.debounce(search, 300); // 300毫秒后执行

// 假设这是一个输入框元素的事件处理器
document.getElementById('search-input').addEventListener('input', (event) => {
  debouncedSearch(event.target.value);
});

在处理窗口大小调整(resize)事件时,可以使用 throttle 函数来限制事件处理函数的调用频率,防止因频繁处理导致性能问题。以下是使用 lodashthrottle 方法来实现这一功能的示例:

import _ from 'lodash';

// 窗口调整大小时要执行的函数
function handleResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;
  console.log('Window size:', width, 'x', height);
}

// 使用lodash的throttle方法来包装handleResize函数
const throttledHandleResize = _.throttle(handleResize, 200); // 每200毫秒最多执行一次

// 添加窗口大小调整的事件监听器
window.addEventListener('resize', throttledHandleResize);

image.png

全局自定义指令实现节流防抖

  1. 定义节流函数:创建一个节流函数,该函数确保在指定的时间间隔内最多执行一次函数。
  2. 创建自定义指令:使用 Vue 的 directive 方法来注册全局自定义指令。

示例:

// 定义一个节流函数
function throttle(func, limit) {
    let lastFunc;
    let lastRan;
    return function() {
        const context = this;
        const args = arguments;
        if (!lastRan) {
            func.apply(context, args);
            lastRan = Date.now();
        } else {
            clearTimeout(lastFunc);
            lastFunc = setTimeout(function() {
                if ((Date.now() - lastRan) >= limit) {
                    func.apply(context, args);
                    lastRan = Date.now();
                }
            }, limit - (Date.now() - lastRan));
        }
    }
}

// 注册一个全局自定义指令 `v-throttle`  --vue2版本 
Vue.directive('throttle', {
    inserted: function(el, binding) {
        let throttleFunction = throttle(binding.value, binding.arg || 2000); // 默认 2000 毫秒
        el.addEventListener('click', throttleFunction);
    },
    unbind: function(el, binding) {
        el.removeEventListener('click', binding.value);
    }
});

// 注册一个全局自定义指令 `v-throttle`  --vue3版本 
Vue.directive('throttle', {
    mounted: function(el, binding) {
        let throttleFunction = throttle(binding.value, binding.arg || 2000); // 默认 2000 毫秒
        el.addEventListener('click', throttleFunction);
    },
    unmounted: function(el, binding) {
        el.removeEventListener('click', binding.value);
    }
});
// 定义一个防抖函数
function debounce(func, delay) {
    let timeout;
    return function() {
        const context = this;
        const args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(context, args);
        }, delay);
    }
}

// 注册一个全局自定义指令 `v-debounce` --vue2版本 
Vue.directive('debounce', {
    inserted: function(el, binding) {
        let debounceFunction = debounce(binding.value, binding.arg || 300); // 默认 300 毫秒
        el.addEventListener('keyup', debounceFunction);
    },
    unbind: function(el, binding) {
        el.removeEventListener('keyup', binding.value);
    }
});

// 注册一个全局自定义指令 `v-debounce` --vue3版本 
Vue.directive('debounce', {
    mounted: function(el, binding) {
        let debounceFunction = debounce(binding.value, binding.arg || 300); // 默认 300 毫秒
        el.addEventListener('keyup', debounceFunction);
    },
   unmounted: function(el, binding) {
        el.removeEventListener('keyup', binding.value);
    }
});


注意事项: 全局自定义指令在vue2和vue3中不同,在 Vue 3 中,自定义指令的生命周期钩子确实发生了一些变化。以下是详细的解释:

1. 指令的生命周期钩子

Vue 2 vs Vue 3

  • Vue 2 中,指令的生命周期钩子主要包括 bindinsertedupdatecomponentUpdatedunbind
  • Vue 3 中,这些钩子被简化和重命名为 createdbeforeMountmountedbeforeUpdateupdatedbeforeUnmountunmounted

2. 绑定和解绑事件监听器

在 Vue 3 中,我们通常使用 mountedunmounted 钩子来绑定和解绑事件监听器。这样做的目的是确保当元素被插入或移除 DOM 时,我们能够正确地添加或移除事件监听器。

示例解释

  • 绑定事件监听器(mounted) : 当元素被插入 DOM 时(在 mounted 钩子中),我们绑定事件监听器。这样可以确保事件监听器在元素实际存在于页面上时才会绑定。
  • 解绑事件监听器(unmounted) : 当元素从 DOM 中移除时(在 unmounted 钩子中),我们解绑事件监听器。这是为了防止内存泄漏,因为如果不解绑,事件监听器会继续存在,即使元素已经从页面上移除。

使用指令

现在,您可以在任何 Vue 组件的模板中使用 v-throttlev-debounce

<!-- 使用节流指令 -->
<button v-throttle:3000="myMethod">Click me</button>

<!-- 使用防抖指令 -->
<input v-debounce:300="myInputMethod">