🌟秒杀!节流(Throttle)!前端常见手写题!

335 阅读7分钟

🧑‍💻秒杀!前端常见手写题!-HowieCong

一、节流

  • 节流(Throttle):是一种控制函数调用频率的技术,当一个函数在短时间内被频繁触发时,节流函数会限制该函数在一定时间间隔内只能执行一次,避免函数被过度调用,从而优化性能

  • 实现思路

    • 时间戳法:通过记录上一次函数执行的时间戳,与当前时间戳进行比较,如果时间差大于设定的间隔时间,则执行函数,并更新时间戳

    • 定时器法:使用定时器,在函数第一次执行时设置定时器,当定时器到期后执行函数,并重置定时器

    • 结合时间戳和定时器法:结合时间戳和定时器的优点,确保函数在最后一次触发后也能执行,并且在时间间隔内只执行一次

二、编码实现

(1)时间戳法

// func 是需要节流处理的函数,wait是时间间隔
function throttleByTimestamp(func,wait){
    // 记录上一次函数执行的时间
    let previous = 0;
    // 返回一个新的函数,用于执行节流操作
    return function(){
        // 获取当前时间戳
        const now = Date.now();
        // 保存函数调用时的上下文
        const context = this;
        // 保存函数调用时的参数
        const args = arguments;
        // 如果当前时间与上一次执行时间的间隔大于等于wait
        if(now - previous >= wait){
            // 执行传入的函数,并传入正确的上下文和参数
            func.apply(context,args);
            // 更新上一次执行时间为当前时间
            previous = now;
        }
    };
}
// 测试示例 
function exampleFunction() { 
    console.log('Function executed'); 
} 
// 创建一个节流后的函数,时间间隔为 1000 毫秒 
const throttledFunction = throttleByTimestamp(exampleFunction, 1000); 
// 模拟频繁触发事件 
setInterval(throttledFunction, 200);

(2)定时器法

// func
function throttleByTimer(func,wait){
    //
    let timer - null;
    // 
    return function(){
        // 
        const context = this;
        const args = arguments;
        if(!timer){
            timer = setTimeout(() => {
                func.apply(context,args);
                timer = null;
            },wait);
        }
    };
}

// 测试
function exampleFunction() { 
console.log('Function executed');
} 
// 创建一个节流后的函数,时间间隔为 1000 毫秒 
const throttledFunction = throttleByTimer(exampleFunction, 1000); 
// 模拟频繁触发事件 
setInterval(throttledFunction, 200);

(3)结合时间戳和定时器法

// func 是需要节流处理的函数,wait 是时间间隔
function throttle(func, wait) { 
    // 记录上一次函数执行的时间 
    let previous = 0; 
    // 用于存储定时器的变量 
    let timer = null; 
    // 返回一个新的函数,用于执行节流操作 
    return function() { 
        // 获取当前时间戳 
        const now = Date.now(); 
        // 保存函数调用时的上下文 
        const context = this; 
        // 保存函数调用时的参数 
        const args = arguments; 
        // 计算距离下一次可执行时间还剩多少 
        const remaining = wait - (now - previous); 
        // 如果剩余时间小于等于 0 或者已经超出了 wait 时间 
        if (remaining <= 0 || remaining > wait) { 
            // 如果定时器存在,清除它 
            if (timer) { 
                clearTimeout(timer); 
                timer = null; 
            } 
            // 执行传入的函数,并传入正确的上下文和参数 
            func.apply(context, args); 
            // 更新上一次执行时间为当前时间 
            previous = now; 
        } else if (!timer) { 
            // 如果剩余时间大于 0 且定时器不存在 
            // 设置一个新的定时器,在剩余时间后执行函数 
            timer = setTimeout(() => { 
                // 更新上一次执行时间 
                previous = Date.now(); 
                // 执行函数 
                func.apply(context, args); 
                // 执行完函数后将定时器置为 null 
                timer = null; 
            }, remaining); 
        } 
    }; 
} 

// 测试示例 
function exampleFunction() { 
console.log('Function executed'); 
} 
// 创建一个节流后的函数,时间间隔为 1000 毫秒 
const throttledFunction = throttle(exampleFunction, 1000); 
// 模拟频繁触发事件 
setInterval(throttledFunction, 200);

三、讨论

(1)时间戳实现和定时器实现的节流有啥区别?

  • 执行时机:时间戳实现的节流函数在事件触发时会立即执行函数,然后在规定时间间隔内不再执行;定时器实现的节流函数在事件触发后会等待一段时间才执行函数,并且在等待期间再次触发事件不会影响函数的执行时间

  • 最后一次触发:时间戳实现的节流函数在最后一次触发事件时,如果时间间隔未达到规定时间,不会执行函数;定时器实现的节流函数在最后一次触发事件后,会在规定时间后执行一次函数

(2)结合时间戳和定时器的节流有啥优势?

  • 结合时间戳和定时器的节流函数综合了两者的优点

  • 既可以在事件触发时立即执行函数(类似时间戳实现),又可以保证在最后一次触发事件后,无论是否达到规定时间间隔,都会在合适的时间执行一次函数(类似定时器实现),避免了时间戳实现中最后一次触发可能不执行的问题和定时器实现中首次触发不立即执行的问题

(3)如果需要在节流函数中取消节流操作,如何做?

  • 可以在节流函数中添加一个取消方法
function throttle(func, wait) { 
    let previous = 0; 
    let timer = null; 
    const throttled = function() { 
        const now = Date.now(); 
        const context = this; 
        const args = arguments; 
        const remaining = wait - (now - previous); 
        if (remaining <= 0 || remaining > wait) { 
            if (timer) { 
                clearTimeout(timer); 
                timer = null;
            } 
            func.apply(context, args); 
            previous = now;
        } else if (!timer) { 
            timer = setTimeout(() => { 
            previous = Date.now(); 
            func.apply(context, args); 
            timer = null;
        }, remaining);
    } 
}; 

// 添加取消方法 
throttled.cancel = function() { 
clearTimeout(timer); 
timer = null; 
previous = 0;
}; 
return throttled; 
} 

// 测试取消功能 
function exampleFunction() { 
    console.log('Function executed'); 
} 
const throttledFunction = throttle(exampleFunction, 1000); 
// 模拟触发事件 
setInterval(throttledFunction, 200); 
// 取消节流 
throttledFunction.cancel();

(4)节流函数的时间复杂度和空间复杂度是多少?

  • 时间复杂度:每次调用节流函数时,主要操作是比较时间、清除定时器和设置新的定时器,这些操作的时间复杂度都是O(1),因此节流函数的时间复杂度是O(1)

  • 空间复杂度:主要的空间开销是存储上一次执行时间和定时器的变量,无论输入的函数和时间间隔如何,只需要常数级的额外空间,因此空间复杂度也是O(1)

(5)节流和防抖的区别?

  • 节流:在一定时间间隔内只触发一次函数执行,无论事件触发频率多高,都会按照固定频率执行

  • 防抖:在事件被触发后等待一段时间,如果在这段时间内没有再次触发该事件,则执行函数;如果在这段时间内再次触发了该事件,则重新计时

(6)节流的适用场景有哪些?

  • 滚动事件:如滚动加载更多内容

  • 窗口调整:如窗口大小改变时的布局调整

  • 按钮点击:如防止频繁点击提交按钮

(7)如何选择节流的时间间隔

  • 根据具体需求和用户体验来选择,例如滚动事件通常选择100-200毫秒,窗口调整事件可以选择200-300毫秒

(8)节流函数的优缺点是什么

  • 优点:减少不必要的函数调用,提高性能

  • 缺点:可能会导致函数执行的延迟,影响用户体验

❓其他

1. 疑问与作者HowieCong声明

  • 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong

  • 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!

2. 作者社交媒体/邮箱-HowieCong