javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS节流防抖

JS面试之手写节流防抖详解

作者:前端平水

作为一个程序员,代码实现才是能力体现,在大部分面试的时候,我们都会被要求手写代码实现一个功能,本文总结了一下经常被面试官问到的节流和防抖功能的实现,分享给有需要的小伙伴

前言

作为一个程序员,代码实现才是能力体现,在大部分面试的时候,我们都会被要求手写代码实现一个功能,这需要有良好的代码习惯和思路,有时候我们也可以多去看看和理解一些经常用到的api源代码,这是有帮助的。这里我总结了一下经常被面试官问到的节流和防抖功能的实现,分享给有需要的小伙伴

防抖

防抖,就是防止频繁重复的触发某个事件。比如当我们点击一个按钮触发一个事件时,可能会连续点了几次,那么会连续多次触发事件函数,这是一种浪费,尤其是对于异步请求数据,连续的请求数据很大程度会影响性能和用户体验。所以,防抖,就是在触发一次函数后规定时间内没有再次触发,再执行该事件函数,若多次在规定时间内重复触发,只执行最后一次触发

实现思路

首先,我们要知道,防抖函数是一个工具函数,我们需要将目标事件函数作为参数传入防抖函数,防抖函数内要返回一个调用了目标函数的函数,当调用防抖函数时,让防抖函数调用目标事件函数,来实现防抖功能。其次,假如从一个中级程序员角度出发,应该想到当我们把自己写的防抖函数作为一个api给初级程序员使用时,希望可以主动设置防抖的时间间隔,所以这个时间间隔也应该是防抖函数的一个参数。

function debounce(fn,wait){
    return function(){
        fn()
    }
}

在这个防抖函数内,我们要调用这个传入的目标事件函数,除此之外,我们还要根据参数设置防抖等待的时间,触发一次函数后,要等待该时间间隔再执行该目标事件函数。所以我们想到使用计时器setTimeout(),调用目标函数,并且设置等待时间。当我们在等待时间内再次触发防抖函数,就需要清除之前的计时器,重新等待时间,那么就需要给定时器定义变量名,且调用清除定时器函数。

*注:定义定时器变量名需要在返回的函数外部,形成闭包,否则每次触发防抖函数调用目标事件函数会重新定义一个同名空变量,导致无法清除之前定时器

function debounce(fn,wait){
    let timeout
    return function(){
        clearTimeout(timeout)
        timeout = setTimeout(fn,wait)
    }
}

到这里我们已经完成了大体功能,之后需要我们考虑更多使用这个防抖函数时可能遇到细节情况:

比如定义目标事件函数时,可能会使用this来指向触发这个事件函数的DOM结构,但是让该目标事件函数在防抖函数中的定时器中调用时,会改变该函数内的this指向window,影响到原函数,所以就需要改正调用该目标函数的this指向。所以可以使用显示绑定(call、apply、bind)来维护目标事件函数的this指向。在定时器调用目标事件函数时,将该函数显示绑定到防抖函数的this,因为当防抖函数被触发时,它的this也是指向触发这个函数的DOM结构

function debounce(fn,wait){
    let timeout
    return function(){
        clearTimeout(timeout)
        timeout = setTimeout(()=>{ //改成箭头函数,否则不能直接接.call()
            fn.call(this)
        },wait)
    }
}

还需要考虑到定义的目标事件函数本身也可能需要传递参数,比如可能是事件参数e,也可能有多个参数。所以我们需要解构出目标事件函数可能接收的所有函数。使用防抖函数调用目标事件函数时,只能将需要传递的参数传递给防抖函数,再从防抖函数解构出参数,来传递给目标事件函数。arguments就是用来代表函数接受的所有参数,我们就可以用它解构防抖函数接收到的参数。

*注:arguments是一个类数组,需要将其解构出来再变成数组,然后作为参数传递给目标函数。并且辅助函数call()接收参数的方式是逐个接收,所以我们需要改成apply(),来接受数组作为参数

function debounce(fn,wait){
    let timeout
    return function(){
        let args = [...arguments]
        clearTimeout(timeout)
        timeout = setTimeout(()=>{
            fn.apply(this,args)
        },wait)
    }
}

最后,还需要考虑到,定义的目标事件函数可能有返回值,所以我们还需要定义一个变量来接收目标时间函数调用后的返回值,并返回这个变量

function debounce(fn,wait){
    let timeout,result //同样定义在返回函数外,形成闭包
    return function(){
        let args = [...arguments]
        clearTimeout(timeout)
        timeout = setTimeout(()=>{
            result = fn.apply(this,args)
        },wait)
        return result
    }
}

到这里,一个完整的防抖函数就实现了,我们可以尝试简单验证一下,源代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">0</button>
    <script>
        let count = 0
        let btn = document.getElementById("btn")
        function add(){
            this.innerHTML = ++count
        }
        function debounce(fn,wait){
            var timeout,result
            return function(){
                let args = [...arguments]
                clearTimeout(timeout)
                timeout = setTimeout(()=>{
                    result = fn.apply(this,args)
                },wait)
                return result
            }
        }
        btn.addEventListener('click',debounce(add,1000))
    </script>
</body>
</html>

节流

节流,和防抖类似,都是防止一些不必要的操作,区别就是,防抖是在规定时间内一直重复触发函数,就一直不执行,直到在规定时间内没有再次触发,才执行;而节流是在规定时间内一直重复触发函数,那么该规定时间内只会执行其中一次。比如,当点击触发一个目标事件函数,可以直接执行这个函数,但是当我们在规定时间内又多次重复触发该函数,那么就不执行之后的触发,而规定时间之后又有触发该函数,就又执行该函数,并且从这个函数触发的时间开始重新计算时间,之后规定时间内的重复触发也不会被执行,这就是节流

实现思路

节流的实现思路大体一样,是一个工具函数,需要传入目标事件函数和规定时间作为参数,并且返回一个函数调用这个目标事件函数,但是这里不需要使用定时器setTimeout(),需要定义一个开始时间,当点击触发一个函数时,将此时的时间赋值给开始时间,之后每次触发都判断此时距离开始时间间隔是否超过规定时间,若超过就执行函数,并且将当前时间又赋值给开始时间,若未超过,则不执行函数也不重新赋值开始时间

function throttle(fn,wait){
    let preTime = 0  //同样使用闭包,保留这个变量
    return function(){
        let now = +new Date()  //一元运算符+改为秒数时间
        if(now - preTime > wait){
            fn()
            preTime = now
        }
    }
}

另外,同样需要考虑目标事件函数中的this指向,以及目标事件函数本身需要接收的参数,和可能有返回值得情况。值得注意的是,虽然这里没有使用setTimeout()定时器,但是函数调用在返回出来的函数中,其内部this指向也会被改变,受到影响,所以需要用apply()辅助函数显示绑定,改正this指向

function throttle(fn,wait){
    let preTime = 0,result
    return function(){
        let args = [...arguments]
        let now = +new Date()
        if(now - preTime > wait){
            result = fn.apply(this,args)
            preTime = now
        }
        return result
    }
}

到这里,也完成了节流函数的手写实现,可以测试一下,源代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">0</button>
    <script>
        let count = 0
        let btn = document.getElementById("btn")
        function add(){
            this.innerHTML = ++count
        }
        function throttle(fn,wait){
            let preTime = 0,result
            return function(){
                let args = [...arguments]
                let now = +new Date()
                if(now - preTime > wait){
                    result = fn.apply(this,args)
                    preTime = now
                }
                return result
            }
        }
        btn.addEventListener('click',throttle(add,1000))
    </script>
</body>
</html>

总结

在面试中,要求手写实现功能是经常遇到的,而防抖和节流函数又是经常被要求书写的函数之一,大家应该也会觉不难,当然写出来也是帮助有需要的小伙伴,希望大家有所收获,面试一举拿下

到此这篇关于JS面试之手写节流防抖详解的文章就介绍到这了,更多相关JS节流防抖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文