面试官:请说说JS中的防抖和节流

简介: 面试官:请说说JS中的防抖和节流

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:web前端面试题库

前言

为什么要做性能优化?性能优化到底有多重要?   性能优化是为了提供更好的用户体验加快网站加载速度提高搜索引擎排名节省服务器资源适应多种设备和网络环境等方面的需求。通过不断优化性能,可以提高用户满意度、增加网站流量提高业务效果。

同时性能优化是把双刃剑,有好的一面也有坏的一面。好的一面就是可以能提升网站性能,坏的一面就是配置多,代码复杂,或者要遵守的规则太多。并且某些性能优化规则并不适用所有场景,所以也并不是一味的追求性能优化,而是需要谨慎使用。

防抖和节流JavaScript 中常用的两种性能优化方式。面试中我们也会经常碰到。它们的作用是减少函数的执行次数,以提高代码的性能。

在进行窗口的resize、scroll、输出框内容校验等操纵的时候,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常之差。那么为了前端性能的优化也为了用户更好的体验,就可以采用防抖(debounce)和节流(throttle)的方式来到达这种效果,减少调用的频率。

防抖(Debounce)

1. 情景

  • 有些场景事件触发的频率过高(mousemoveonkeydownonkeyuponscroll),例如文本编辑器实时保存,当无任何更改操作一秒后进行保存;调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖。
  • 回调函数执行的频率过高也会有卡顿现象。 可以一段时间过后进行触发去除无用操作

2. 防抖原理

一定在事件触发 n 秒后才执行,如果在一个事件触发的 n 秒内又触发了这个事件,以新的事件的时间为准,n 秒后才执行,等触发事件 n 秒内不再触发事件才执行。

官方解释:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。(ps:个人理解这就相当于游戏中的回城键,只有读秒到规定时间点才会触发回城,期间任意时间打断都会重新计时。)

3. 防抖函数简单实现

    //简单的防抖函数
    function debounce(func, wait, immediate) {
        //定时器变量
        var timeout;
        return function () {
            //每次触发scrolle,先清除定时器
            clearTimeout(timeout);
            //指定多少秒后触发事件操作handler
            timeout = setTimeout(func, wait);
        };
    };
    //绑定在scrolle事件上的handler
    function handlerFunc() {
        console.log('Success');
    }
    //没采用防抖动
    window.addEventListener('scroll', handlerFunc);
    //采用防抖动
    window.addEventListener('scrolle', debounce(handlerFunc, 1000));

4. 防抖函数演化过程

(1)this event绑定问题

    //以闭包的形式返回一个函数,内部解决了this指向的问题,event对象传递的问题
    function debounce(func, wait) {
        var timeout;
        return function () {
            var context = this;
            var args = arguments;
            clearTimeout(timeout)
            timeout = setTimeout(function () {
                func.apply(context, args)
            }, wait);
        };
    };

(2) 立即触发问题

    //首次触发执行,再次触发以后开始执行防抖函数
    //使用的时候不用重复执行这个函数,本身返回的函数才具有防抖功能
function debounce(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        if(timeout) clearTimeout(timeout);
        // 是否在某一批事件中首次执行
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                timeout = null;
                func.apply(context, args)
                immediate = true;
            }, wait);
            if (callNow) {
                func.apply(context, args)
            }
            immediate = false;
        } else {
            timeout = setTimeout(function() {
                func.apply(context, args);
                immediate = true;
            }, wait);
        }
    }
}

(3)返回值问题

function debounce(func, wait, immediate) {
    var timeout, result;
    return function () {
        var context = this, args = arguments;
        if (timeout)  clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
            if (callNow) result = func.apply(context, args);
        } else {
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
        }
        return result;
    }
}

(4)取消防抖,添加cancel方法

function debounce(func, wait, immediate) {
    var timeout, result;
    function debounced () {
        var context = this, args = arguments;
        if (timeout)  clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
            if (callNow) result = func.apply(context, args);
        } else {
            timeout = setTimeout(function() {
                result = func.apply(context, args)
            }, wait);
        }
        return result;
    }
    debounced.cancel = function(){
        cleatTimeout(timeout);
        timeout = null;
    }
    return debounced;
}

节流(Throttle)

1.情景

  • 图片懒加载
  • ajax数据请求加载

2.节流原理

如果持续触发事件,每隔一段时间只执行一次函数,控制了事件发生的频率。

官方解释:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。(s:个人理解这就相当于游戏中的射击,就算你高频点击,子弹只会在固定时间段内射出。)

3.节流实现-时间戳和定时器

  • 时间戳
    var throttle = function (func, delay) {
        var prev = Date.now()
        return function () {
            var context = this;
            var args = arguments;
            var now = Date.now();
            if (now - prev >= delay) {
                func.apply(context, args);
                prev = Date.now();
            }
        }
    }
    function handle() {
        console.log(Math.random());
    }
    window.addEventListener('scroll', throttle(handle, 1000));
  • 定时器
    var throttle = function (func, delay) {
        var timer = null;
        return function () {
            var context = this;
            var args = arguments;
            if (!timer) {
                timer = setTimeout(function () {
                    func.apply(context, args);
                    timer = null;
                }, delay);
            }
        }
    }
    function handle() {
        console.log(Math.random());
    }
    window.addEventListener('scroll', throttle(handle, 1000))

4.节流函数的演化过程

  • 时间戳触发
//在开始触发时,会立即执行一次; 如果最后一次没有超过 wait 值,则不触发
function throttle(func, wait) {
    var context, args;
    var previous = 0; // 初始的时间点,也是关键的时间点
    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}
  • 定时器触发
//首次不会立即执行,最后一次会执行,和用时间戳的写法互补。
function throttle(func, wait){
    var context, args, timeout;
    return function() {
        context = this;
        args = arguments;
        if(!timeout) {
            timeout = setTimeout(function(){
                func.apply(context, args);
                timeout = null;
            }, wait);
        }
    }
}
  • 结合时间戳和定时器
function throttle(func, wait) {
    var timer = null;
    var startTime = Date.now();  
    return function(){
        var curTime = Date.now();
        var remaining = wait-(curTime-startTime); 
        var context = this;
        var args = arguments;
        clearTimeout(timer);
        if(remaining<=0){ 
            func.apply(context, args);
            startTime = Date.now();
        }else{
            timer = setTimeout(fun, remaining);  // 如果小于wait 保证在差值时间后执行
        }
    }
}

总结

防抖:原理是维护一个定时器,将很多个相同的操作合并成一个。规定在delay后触发函数,如果在此之前触发函数,则取消之前的计时重新计时,只有最后一次操作能被触发

节流:原理是判断是否达到一定的时间来触发事件。某个时间段内只能触发一次函数

区别:防抖只会在最后一次事件后执行触发函数,节流不管事件多么的频繁,都会保证在规定时间段内触发事件函数。

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:web前端面试题库

相关文章
|
12天前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
33 5
|
1月前
|
JavaScript
JS封装节流函数
JS封装节流函数
15 0
|
1天前
|
JavaScript 前端开发 测试技术
「一劳永逸」送你21道高频JavaScript手写面试题(上)
「一劳永逸」送你21道高频JavaScript手写面试题
14 0
|
1月前
|
JavaScript
|
1月前
|
设计模式 JavaScript 前端开发
最常见的26个JavaScript面试题和答案
最常见的26个JavaScript面试题和答案
40 1
|
1月前
|
存储 JavaScript 前端开发
【JavaScript】面试手撕浅拷贝
引入 浅拷贝和深拷贝应该是面试时非常常见的问题了,为了能将这两者说清楚,于是打算用两篇文章分别解释下深浅拷贝。 PS: 我第一次听到拷贝这个词,有种莫名的熟悉感,感觉跟某个英文很相似,后来发现确实Copy的音译,感觉这翻译还是蛮有意思的
45 6
|
1月前
|
JavaScript 前端开发
【JavaScript】面试手撕节流
上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。
51 3
|
2月前
|
前端开发 JavaScript UED
【JavaScript】面试手撕防抖
防抖: 首先它是常见的性能优化技术,主要用于处理频繁触发的浏览器事件,如窗口大小变化、滚动事件、输入框内容改变等。在用户连续快速地触发同一事件时,防抖机制会确保相关回调函数在一个时间间隔内只会被执行一次。
36 0
|
2月前
|
前端开发 JavaScript 算法
【JavaScript】面试手撕数组排序
这章主要讲的是数组的排序篇,我们知道面试的时候,数组的排序是经常出现的题目。所以这块还是有必要进行一下讲解的。笔者观察了下前端这块的常用算法排序题,大概可以分为如下
24 2
|
2月前
|
JavaScript 前端开发 索引
【JavaScript】面试手撕数组原型链(易)
续借上文,这篇文章主要讲的是数组原型链相关的考题,有些人可能会纳闷,数组和原型链之间有什么关系呢?我们日常使用的数组forEach,map等都是建立在原型链之上的。举个🌰,如我有一个数组const arr = [1,2,3]我想要调用arr.sum方法对arr数组的值进行求和,该如何做呢?我们知道数组没有sum函数,于是我们需要在数组的原型上定义这个函数,才能方便我们调用,具体代码如下。接下来我们就是采用这种方式去实现一些数组常用的方法。
39 6