JS进阶篇:函数防抖和节流


throttle(节流):在规定时间内,保证执行/触发一次该函数。原理是通过判断是否到达一定时间来触发函数。

debounce(防抖):将几次操作合并为一此操作进行。当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始计时。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。

比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

通俗的来讲

我们在进行窗口的resizescroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。

此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

防抖

定义:将几次操作合并为一此操作进行

在前端中有一些事件会频繁的触发容易造成页面卡顿

例如:

  1. window 的 resize、scroll
  2. mousedown、mousemove
  3. keyup、keydown
  4. input 的 input 事件

搜索框搜索输入,只需用户最后一次输入完,再发送请求;

用户名、手机号、邮箱输入验证;

浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。

立即执行版:

//防抖(立即执行)
function debounce(fn, wait = 500) {
    let timerId;
    let flag = true;
    return function () {
        clearTimeout(timerId);
        if (flag) {
            fn.apply(this, arguments);
            flag = false;
        } else {
            timerId = setTimeout(function () {
                flag = true;
            }, wait);
        }
    };
}

非立即执行版:

//防抖(非立即执行)
function debounce(fn, wait = 500) {
    let timerId;
    return function () {
        if (timerId) {
            clearTimeout(timerId);
        }
        timerId = setTimeout(() => {
            fn.apply(this, arguments);
        }, wait);
    };
}

合并版:

//防抖(合并版)
function debounce_merge(fn, wait = 500, isImmediate = false) {
    let timerId = null;
    let flag = true;
    if (isImmediate) {
        return function () {
            clearTimeout(timerId);
            if (flag) {
                fn.apply(this, arguments);
                flag = false;
            } else {
                timerId = setTimeout(function () {
                    flag = true;
                }, wait);
            }
        };
    } else {
        return function () {
            if (timerId) {
                clearTimeout(timerId);
            }
            timerId = setTimeout(() => {
                fn.apply(this, arguments);
            }, wait);
        };
    }
}

节流

定义:让事件在一定时间内只执行一次

实际运用:搜索框input事件,例如要支持输入实时搜索可以使用节流方案。

节流throttle代码(时间戳):

function throttle(fn, wait = 500) {
    let startTime = 0;
    return function () {
        let endTime = Date.now();
        if (endTime - startTime > wait) {
            fn.apply(this, arguments);
            startTime = endTime;
        }
    };
}
// var throttle = function(fn, delay) {
//   var prev = Date.now();
//   return function() {
//     var context = this;
//     var args = arguments;
//     var now = Date.now();
//     if (now - prev >= delay) {
//       fn.apply(context, args);
//       prev = Date.now();
//     }
//   }
// }
// function handle() {
//   console.log(Math.random());
// }
// window.addEventListener('scroll', throttle(handle, 1000));

节流throttle代码(定时器):

function throttle(fn, wait) {
    let timeId = null;
    return function () {
        if (!timeId) {
            timeId = setTimeout(function () {
                fn.apply(this, arguments);
                timeId = null;
            }, wait);
        }
    };
}
// // 节流throttle代码(定时器):
// var throttle = function(fn, delay) {
//     var timer = null;
//     return function() {
//         var context = this;
//         var args = arguments;
//         if (!timer) {
//             timer = setTimeout(function() {
//                 fn.apply(context, args);
//                 timer = null;
//             }, delay);
//         }
//     }
// }
// function handle() {
//     console.log(Math.random());
// }
// window.addEventListener('scroll', throttle(handle, 1000));

节流throttle代码(时间戳+定时器):

// 节流throttle代码(时间戳+定时器):
var throttle = function (fn, delay) {
    var timer = null;
    var startTime = Date.now();
    return function () {
        var curTime = Date.now();
        var remaining = delay - (curTime - startTime);
        var context = this;
        var args = arguments;
        clearTimeout(timer);
        if (remaining <= 0) {
            fn.apply(context, args);
            startTime = Date.now();
        } else {
            timer = setTimeout(fn, remaining);
        }
    };
};
function handle() {
    console.log(Math.random());
}
window.addEventListener("scroll", throttle(handle, 1000));

以上都是非立即执行版本

//节流(立即执行)
function throttle(fn, wait = 500) {
    let timeId = null;
    return function () {
        if (!timeId) {
            timeId = true;
            fn.call(this, ...arguments);
            timeId = setTimeout(function () {
                timeId = null;
            }, wait);
        }
    };
}
//节流(合并)
function throttle_merge(fn, wait = 500, isImmediate = false) {
    let flag = true;
    let timer = null;

    if (isImmediate) {
        return function () {
            if (flag) {
                fn.apply(this, arguments);
                flag = false;
                timer = setTimeout(() => {
                    flag = true;
                }, wait);
            }
        };
    } else {
        return function () {
            if (flag == true) {
                flag = false;
                var timer = setTimeout(() => {
                    fn.apply(this, arguments);
                    flag = true;
                }, wait);
            }
        };
    }
}

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录