[跳跳的面试系列1]面试必问的节流防抖,你了解了么?

286 阅读4分钟

相信很多人在面试的时候都会被问到节流和防函数,那么节流和防抖函数到底是什么呢,在我们的日常工作中又会有哪些场景会使用到呢

防抖函数

防抖函数,听上去总觉得脑海里出现很多让人瑟瑟发抖的表情包

那让我们从一个比较实际的场景触发,比如我们在使用百度搜索的时候,当我们在输入的时候,搜索框下会的推荐关键词列表会实时改变

但是如果当我们每一次输入都请请求一下后端的推荐接口的话,可能会对后端服务造成非常大的压力,而且页面也会因为接口返回的数据不断变化而导致页面的不断redraw,从而页面产生抖动。

在这种场景下用户也并不是需要那么实时结果,而且当用户在快速输入的时候,其实输入到一半的数据往往也不是用户想要的最终内容。

所以我们的需求就是 需要在用户快速输入的时候先不发送接口请求,而在用户停止输入的时候尽快发送(停止输入表示用户可能已经输入了想要的搜索内容)

因为我们无法非常准确知道什么时候是用户已经完成输入了,但是我们可以设定一个时间,比如500ms内用户没有再次输入了,那么我们就认为用户已经完成了输入

const throttleTime = 500;
let timer = null
function onInput() {
    // 清除之前的定时器(如果在500ms内触发的第二次输入,那么之前的定时器就会被清除,如果是500ms后的,因为请求已经发送了,也可以直接清除)
    clearTimeout(timer)
    // 设定一个定时器500ms后发送请求
    timer = setTimeout(sendRequest, throttleTime);
}

简单的几行代码就已经完成了我们需求,但是现在有一个问题,我们的代码最快也要在用户输入的第一次以后的500ms,有时候我们想要用户在第一时间先看到一些内容,在视觉体验上会好一些,那么我们需要简单的调整一下

const throttleTime = 500;
let timer = null
function onInput(...args) {
    // 清除之前的定时器(如果在500ms内触发的第二次输入,那么之前的定时器就会被清除,如果是500ms后的,因为请求已经发送了,也可以直接清除)
    // 保存一下当前的参数和作用域
    const that = this;
    if (!timer) {
        sendRequest.apply(this, args)
        // 设定一个无操作的定时器,其实也可以直接设定timer = 1
        timer = setTimeout(() => {}, throttleTime);
    } else {
        clearTimeout(timer)
        // 设定一个定时器500ms后发送请求
        timer = setTimeout(() => {
            sendRequest.apply(this, args)
        }, throttleTime);
    }
}

到这一步,我们发现,我们的timer和我们业务代码onInput()sendRequest()已经完成耦合在一起了,如果有另外有一个业务功能需要使用,又需要设置一遍同样的内容。

那我们需要把我们写的这个功能抽象出来,封装成一个可以被复用的功能

const throttle = function(func, throttleTime) {
    let timer = null;
    return function(...args) {
        const that = this;
        if (!timer) {
            func.apply(this, args)
            // 设定一个无操作的定时器,其实也可以直接设定timer = 1
            timer = setTimeout(() => {}, throttleTime);
        } else {
            clearTimeout(timer)
            // 设定一个定时器500ms后发送请求
            timer = setTimeout(() => {
                func.apply(this, args)
            }, throttleTime);
        }
    }
}

// 使用
$input.onInput(throttle(sendRequest, 500));

到这里我们其实就已经完成了一个基本的节流函数了

节流函数可以用在一些高频触发的场景下,用来控制回调函数的触发次数,就像我们一直按住枪的开关,只要不松手,子弹就不会射出去

节流函数

节流,从字面上来看就是减少触发的次数,直觉上还是有些不好理解,也不太清楚具体的场景

我们还是从一个例子来看吧,当我们页面刷有很多小视频的时候,我们实时的监听滚动到了什么位置,来判断滚动到了什么位置,来使对应的位置的视频自动播放。

因为页面的滚动的回调非常的频繁,同时回调函数里面获取视频的位置(offsetTop)也是需要一些时间,会导致大量不必要的计算,会导致其他的任务被hold

那我们可以设定一个规则,比如500ms内我们的回调函数只会被触发一次,且是最后一次,就可以使回调的次数大量减少,且不会影响功能。

const debounceTime = 500;
let lastTriggerTime = 0;
function scoller(...args) {
    const now = new Date().getTime();
    // 当触发的时间距离上一次的时间已经超过500ms,那么就可以触发一次
    if (now - lastTriggerTime > debounceTime) {
        videoStart.apply(this, args)
        lastTriggerTime = now
    }
}

我们同样也需要将这个功能封装成一个可以被复用的函数

function debounce(func, debounceTime) {
    let lastTriggerTime = 0;
    return function(...args) {
        const now = new Date().getTime();
        const that = this;
        if (now - lastTriggerTime > debounceTime) {
            func.apply(this, args)
            lastTriggerTime = now
        }
    }
}

window.onscroll(debounce(videoStart, 500));