在JS文件和Vue组件中使用防抖节流函数

如题,最近笔者发现如果将防抖节流函数写成工具类函数放在公共文件中进行调用的话,在一般的JS文件里调用和在Vue组件中调用是有不同的区别的。首先我们参考网上其他资料写一个防抖函数和一个节流函数:

防抖函数(debounce):

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) // 这行代码可不加,详细说明见下文
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

这里简单的说明一下这个防抖函数的主要思路:首先我们如果要对某个函数进行防抖处理(比如说根据用户在搜索框中输入的关键字来向服务器发起请求实时匹配相关的内容) 如下图:

一般我们要对这种http请求函数做防抖处理,如果这类处理不常见的话,可以直接做一个简单的防抖处理:

// vue组件中
data () {
    return {
        timer: null
    }
},
methods: {
    getXX () {
        if (this.timer) {
            clearTimeout(this.timer)
        }
        this.timer = setTimeout(() => {
            // 具体的业务代码
        }, 500) // 假设防抖间隔是500ms
    }
}

但是如果我们在很多地方都需要进行防抖处理的话,此时为了减少函数功能间的耦合,增强代码的可读性和可维护性,我们有必要写一个公共的防抖/节流函数放在utils.js等工具类文件中,那么具体的写法就如同上文的debounce函数一样,这种防抖函数是比较终极的一个版本了,因为其不但含有immediate参数(控制是否先立即执行一次功能函数func),还考虑了this在不同调用环境下的指向问题(使用apply函数绑定this),具体含义可以参考这篇文章:https://www.cnblogs.com/cc-freiheit/p/10827372.html

1. debounce函数在一般JS文件中的用法:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>

<body>
<div id="container"></div>
<button id="button">cancel</button>
<script src="debounce.js"></script>
</body>

</html>

这里给出了一个 id为 container的div,为该div绑定onmousemove 方法,当鼠标在container上移动时就会不断触发onmousemove事件从而增加count的值,在没有加入防抖处理前效果是这样的:

加入防抖处理后: 

// TODO: 立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
// 假设debounce函数写在utils.js文件内
var count = 1;
var container = document.getElementById('container');

function handleUserAction() {
    container.innerHTML = count++;
}

container.onmousemove = utils.debounce(handleUserAction, 3000, true);
 

结果如下:

如图:这里由于将immediate置为true,当鼠标触发mousemove事件时会马上触发一次,此时显示为1,之后不断在div中移动鼠标触发mousemove事件,经防抖函数处理后count一直为1,直至停止触发该事件3s以上,count变为2(最后又因为鼠标再次移动了count变为3)

 这是在debounce函数中含有第16行代码 func.apply(context, args) 情况下的防抖效果,在注释中我们提到这行代码可以不加,那么如果在debounce函数中不加第16行代码效果会有何不同呢?如下图:

在鼠标触发了mousemove事件后(immediate为true),count立即变为了1,之后停止触发mousemove 3秒以上,但是count值最终也没有再加1

也就是说再immediate为true的情况下,第16行代码加与不加的区别在于最后停止频繁触发某一被防抖事件后还会不会再执行一次该事件。笔者认为这里我们可以针对不同的需求加上/删去第16行代码。不过根据笔者之前遇到的情况来看,一般都是需要第16行代码的,比如说上文中的搜索框根据关键词实时联想相关搜索结果的防抖处理就要加。

2. debounce函数在vue组件中的使用

 防抖函数在vue组件中的写法与在一般js文件中的写法略有不同,有时候没有注意到这一点在调用该函数的时候会遇到防抖无效的问题。首先我们同样可以将debounce函数作为一个工具函数放入一个工具类文件中,然后在vue组件中调用该函数:

<!--vue组件的HTML部分-->

    <div id="box" class="welcome">
        <div id="content1" 
            style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;" 
            @mousemove="changeContent">
         </div>
    </div>
// methods函数部分,正确的调用写法
methods: {
        addContent () {
            let content = document.getElementById('content1')
            console.log('content', content)
            content.innerHTML = this.num++
        },
        changeContent: utils._debounce(function() {
            this.addContent()
        }, 2000, true),
}

 如上,根据测试,在vue组件的中调用debounce函数必须以这样的形式调用才有效,否则调用无效

// 这种调用写法无效
changeContent () {
     utils._debounce(function() {
          this.addContent()
     }, 2000, true)
}

具体原因笔者现在还没有完全搞清除,但是推测与vue中的方法存在于其组件实例(每个组件具体的this)上有关 。

最后是节流函数的使用,其实与防抖函数的调用是一样的,只是效果不同,我们应该针对具体需求对相关函数进行防抖/节流处理,这里给出一个节流的参考版本:

// trailing同样控制的是 是否先立即执行一次函数
function throttle (func, wait, trailing) {
    console.log('func', func)
    console.log('执行函数') // 这是闭包,函数只要引入页面就执行了
    let timer = null
    let start = 0
    return function () {
        console.log('func2', func)
        let now = new Date()
        let remaining = wait - (now - start)
        console.log(remaining)
        // 拦截:延迟时间>多次点击间隔时间,执行:多次点击间隔时间>延迟时间
        if ((now - start) >= wait) {
            // 由于setTimeout存在最小时间精度问题,因此会存在到达wait的时间间隔
            // 但之前设置的setTimeout操作还没被执行,因此为保险起见,这里先清理setTimeout操作
            timer && clearTimeout(timer)
            timer = null
            start = now
            console.log('非频繁操作,点击间隔为:',remaining)
            return func()
        } else if (!timer && trailing) {
            // trailing有值时,延时执行func函数
            // 频繁操作,第一次设置定时器之后,之后的不会再走到这里创建定时器
            // 清除问题,只能在第二有效点击的时候才会清除
            timer = setTimeout(() => {
                timer = null
                console.log('频繁操作,定时器延时执行')
                return func
            }, wait)
        }
    }
}

 demo代码演示地址:

防抖: https://codepen.io/zitonzong/pen/jONpBMm

节流:https://codepen.io/zitonzong/pen/QWLBpKp

参考资料:

https://www.cnblogs.com/cc-freiheit/p/10827372.html

https://www.cnblogs.com/fozero/p/11107606.html

发布了24 篇原创文章 · 获赞 20 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/a715167986/article/details/103027861