阅读 993

【性能优化】0202年了,函数节流与防抖还不用起来?

引言

还有一个多月就要过年了。温馨提示:小伙伴们该抢票了。🏮

说到过年,不得不提的就是春运。我们可以从春运中,体验一下节流和防抖思想给社会带来的秩序和效率上的提升。

场景一:安检

地铁过安检,安检员会在入口控制人流。当人流过多时,会及时对后面的人流阻断,起到节流的作用,避免过于拥挤而阻塞安检通过,进一步提高过检效率。

场景二: 拥挤的小巴车。

小巴车不知道大家有没有体验过。 如果陆陆续续有乘客上车,售票员一般是不会轻易发车的。他心里大概会想,等等,再等等,10分钟内还没有乘客,就发车。此时,已上车的乘客心里苦啊。。。当然从运输效率上讲,这种典型的防抖思想的应用,大大提升了运输效率,避免了运输资源的浪费。

原理分析

函数节流和防抖的概念已经老生常谈了。有很多文章都讲到过。如果还不太了解,没关系。本文希望能给到大家比较舒适的理解体验。🙂

假设

事件处理函数为 A

节流(或防抖)函数为 B

则,最终事件触发时,实际执行的是**B(A)**返回的内部函数(闭包)。

节流(Throttle)函数: 对于持续的事件触发,每达到固定时间间隔,执行事件处理函数A

防抖(Debounce)函数: 事件触发停止后开始计时,在固定时间内不再有事件触发,执行事件处理函数A

后边,我们会结合具体示例,加强理解。😬

重要作用

在讲具体应用前,希望大家先问自己几个问题?

  • 为什么会有节流防抖的概念,他们是为了解决什么样的问题?
  • 为了提高代码逼格,日常开发中要不要用起来?
  • ……

看完本文希望大家能得到答案。

作为一名与用户”零距离“的前端开发人员,用户体验这块我们应该拿的sisi的。节流和防抖,就是一种**”讨好“**用户的重要手段。

① 优化用户体验(适时反馈,避免UI渲染阻塞,浏览器卡顿);

② 提升页面性能(避免页面渲染卡顿,减少服务器压力,防范恶意触发);

日常业务开发中,页面中连续触发的事件处理,一般需要使用函数节流和防抖来进行优化。例如: 页面滚动(scroll)改变页面尺寸(resize)表单验证(input)按钮点击(click)、 **鼠标滑动(mousemove)**等。接下来,我们一起来看看具体实现和应用场景。

应用场景

节流

实现

/**
 * 节流函数
 * @param {Function} fn
 * @param {Number} delay
 */
function throttle(fn, delay) {
    var last = 0;
    return function () {
        var now = Date.now();
        if (now - last >= delay) {
            fn.apply(this, arguments);
            last = now;
        }
    }
}
复制代码

应用

假设一个抢优惠劵的业务场景:用户连续点击优惠劵,每点击一次就会得到一张优惠券,最多10张,总量1000张(有限)。双十一刚过,阿里电商2000多亿的现金流量,想必消费者优惠券都抢的够够的。为了抢到更多优惠券,用户常规操作应该是,手动持续点击。而非常规操作,可能会写一个for循环,把这10张券轻轻松松搞到手。

for循环抢券,如下图:

上图显示,执行了1000次抢优惠券逻辑,是不是很暴力!假如每个用户没有最多领10次的限制,1000张优惠券,瞬间会被恶意抢走。全网常规操作的用户,手再快,也不可能胜过一个for循环。😢……

当然,

严谨的抢券响应逻辑,一定不会让这样的事情发生。节流防范了解一下。

// 优惠劵点击,事件处理函数
function snatchCouponFn() {
	console.log('恭喜您,优惠券已到手');
}

// 使用 节流函数(已给出实现,往上找找)包装抢优惠劵的事件处理函数
$coupon.onclick = throttle(snatchCouponFn, 2000)

复制代码

再来看看效果:

最后仅执行了1次抢优惠券逻辑。✌️

通过上边的例子,函数节流不仅提升了页面性能,而且保证了业务程序的安全性。

经验有限,更多方面的应用,就不一一举例了。欢迎大家在评论区留下你的经验,帮助更多的人把节流的思想用起来。🤝

防抖

实现

/**
 * 防抖函数
 * @param {Function} fn 
 * @param {Number} delay 
 */
function debounce(fn, delay) {
    var timer;
    return function() {
        console.log('事件触发');
      	var self = this,
            argumentsBySelf = arguments;
      	clearTimeout(timer);
      	timer = setTimeout(function() {
            fn.apply(self, argumentsBySelf);
      	}, delay);
    }
}
复制代码

应用

常见场景:在填写表单时,检查表单值是否有效。

来看一下我们添加防抖的过程:

//伪代码
function onInputFn() {
    $error.innerHTML = '昵称重复,请换用其他昵称';
    console.log('事件响应');
}
              
$name.oninput = debounce(onInputFn, 2000);
复制代码

依据debounce的实现原理,当表单input连续输入时,内部函数会被连续触发。执行的操作是,清除上一次的定时器 clearTimeout(timer),同时重新设定一个定时器,这样上一次定时器从不会执行,fn 就不会被执行到。但当用户停止向表单继续输入时,达到debounce 调用时设定的时间 delay ,最后一次设定的定时器会被执行,fn 最终被执行,来完成表单值校验操作。

当然,我们常用的方法还有onblur(当表单项失去焦点时触发)来执行表单值校验。但这样不如oninput+debounce 的方案给到用户反馈及时和体验好。

类似校验昵称是否重复这类需求,一般是需要调用后端接口,使用debounce对校验函数做包装后,相比与单纯使用oninput, 可大大减少后端接口的调用次数。

不足

经过上面分别对throrrledebounce的示例介绍,大家也应该发现了他们各自的小瑕疵:

  • 经过Throttle 处理的函数,在用户刚停止触发事件时,很可能,不再会对用户的操作做出任何反馈;

  • 经过Debounce处理的函数,在用户刚开始触发事件时,也并没有给到用户任何的反馈。

但这在很多场景下,可能会导致不良甚至错误的用户体验。所以,能不能在用户刚开始触发时或结束触发时或是长时间触发过程中也能给到用户必要的反馈(事件响应)呢 ? 答案是,可以的。来看一下实现。

/**
 * 加强版——防抖
 * @param {Function} fn
 * @param {Number} delay
 */
function enhancedDebounce(fn, delay) {
    var timer,
        last = 0;

    return function() {
        var self = this,
            argumentsBySelf = arguments,
            now = Date.now();

        if(now - last < delay) {
            clearTimeout(timer);
            timer = setTimout(function() {
            	fn.apply(self, argumentsBySelf);
            }, delay)
        } else {
            fn.apply(this, arguments);
            last = now;
    	}
    }
}
复制代码

应用

…… 笔者后续作补充。欢迎大家评论区留言,分享你的应用故事。

小结

都说细节决定成败。本文是在讨论日常开发中,关于性能优化的小细节,希望大家都能善用到自己code中去。小细节,大作用

本文如有错误或不足,欢迎大家评论指出。助笔者进步。如果帮助到你,感谢留下一个赞哦!

本文首发笔者公众号,想要第一时间获取更多优质好文,欢迎关注公众号「码农coffee」。

Bye!

关注下面的标签,发现更多相似文章
评论