yizihan / blog

Record
1 stars 0 forks source link

Throttle -节流函数 #8

Open yizihan opened 6 years ago

yizihan commented 6 years ago

在浏览器DOM事件里面,有一些事件会随着用户的操作不间断触发。比如:重新调整浏览器窗口大小(resize)、浏览器页面滚动(scroll)、鼠标移动(mousemove)。也就是说用户在触发这些浏览器操作时,脚本里绑定的对应的事件处理方法,这个方法就会不停的触发。

有的时候如果事件处理方法比较庞大,DOM操作比较复杂,还不断的触发此类事件就会造成性能上的损失,导致用户体验下降(UI反应慢、浏览器卡死等)。所以通常我们会给相应事件添加延迟执行的逻辑。

方案一

延迟调用onresize的回调函数

let Count = 0;
let timer = null
function addCount() { console.log(Count++)}
window.onresize = function () {
    clearTimeout(timer);
    timer = setTimeout(function() {
        addCount();
    }, 100)
}

改善建议:产生了一个全局变量timer;为了解决这个问题,需要使用闭包。

方案二

使用一个闭包函数throttle,把timer放在内部并且返回延时处理函数,这样timer变量对外是不可见的,但是内部延时函数触发时还可以访问到timer变量。

let Count = 0;
function addCount() { console.log(Count++)}
function throttle(fn, delay) {
    var timer = null;
    // 返回匿名函数,产生闭包
    return function () {
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn();
        }, delay)
    }
}
window.onresize = throttle(addCount, 100)

throttle被调用后返回的回调函数才是真正的onresize触发时需要调用到的函数。

改善建议:如果用户不断的resize浏览器窗口大小,这时延迟处理函数一次都不会执行。 新功能:当用户触发resize的时候应该在某段时间内至少触发一次;判断条件是取当前的时间毫秒数,每次调用把当前的时间和上一次调用时间相减,然后判断差值如果大于某段时间就直接触发,否则还是走timeout的延迟逻辑。

方案三

添加固定时间执行回调函数的逻辑

let Count = 0;
function addCount() { console.log(Count++)}
function throttle(fn, delay, atlest) {
    var timer = null;       // 记录上一次延时事件的标识
    var previous = null;    // 记录上一次执行时间的标识

    return function () {
        var now = +new Date();          // 毫秒字符串
        if(!previous) previous = now;       // 将previous重置到当前时间

        if (atlest && (now - previous) > atlest) {  // 当时间差大于设定的时间段时
            fn();                   // 强制执行一次
            previous = now;             // 再次将previous重置到当前时间
        } else {
            clearTimeout(timer);            // 清除上次的延时事件
            timer = setTimeout(function() {         // 重新设定延时事件
                fn();
            }, delay)
        }
    }
}
window.onresize = throttle(addCount, 500, 1000)

参考文章: JavaScript 节流函数 Throttle 详解