JavaScript性能优化之函数节流(throttle)与函数去抖(debounce)

函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用。

函数节流的原理挺简单的,估计大家都想到了,那就是定时器。当我触发一个时间时,先setTimout让这个事件延迟一会再执行,如果在这个时间间隔内又触发了事件,那我们就clear掉原来的定时器,再setTimeout一个新的定时器延迟一会执行,就这样。

以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

1. window对象的resize、scroll事件

2. 拖拽时的mousemove事件

3. 射击游戏中的mousedown、keydown事件

4. 文字输入、自动完成的keyup事件

实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。

throttle 和 debounce 是解决请求和响应速度不匹配问题的两个方案。二者的差异在于选择不同的策略。

throttle 等时间 间隔执行函数。

debounce 时间间隔 t 内若再次触发事件,则重新计时,直到停止时间大于或等于 t 才执行函数。

一、throttle函数的简单实现

function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
timer; return function () {
var context = scope || this;
var now = +new Date(),
args = arguments;
if (last && now - last + threshhold < 0) {
// hold on to it
clearTimeout(deferTimer);
timer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};}

调用方法

$('body').on('mousemove', throttle(function (event)
{
console.log('tick');
}, 1000));

二、debounce函数的简单实现

function debounce(fn, delay)
{
var timer = null;
return function ()
{
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};}

调用方法

$('input.username').keypress(debounce(function (event)
{
// do the Ajax request
}, 250));

三、简单的封装实现

/** * throttle * @param fn, wait, debounce */var throttle = function ( fn, wait, debounce ) {
var timer = null, // 定时器
t_last = null, // 上次设置的时间
context, // 上下文
args, // 参数
diff; // 时间差
return funciton () {
var curr = + new Date();
var context = this, args = arguments;
clearTimeout( timer );
if ( debounce ) { // 如果是debounce
timer = setTimeout( function () {
fn.apply( context, args );
}, wait );
} else { // 如果是throttle
if ( !t_last ) t_last = curr;
if ( curr - t_last >= wait ) {
fn.apply( context, wait );
context = wait = null;
}
} }}/** * debounce * @param fn, wait */var debounce = function ( fn, wait )
{
return throttle( fn, wait, true );
}

小结:这两个方法适用于会重复触发的一些事件,如:mousemove,keydown,keyup,keypress,scroll等。
如果只绑定原生事件,不加以控制,会使得浏览器卡顿,用户体验差。为了提高js性能,建议在使用以上及类似事件的时候用函数节流或者函数去抖加以控制。

四、underscore v1.7.0相关的源码剖析                          

1. _.throttle函数

_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 定时器
var previous = 0;
// 上次触发的时间
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function()
{
var now = _.now();
// 第一次是否执行
if (!previous && options.leading === false) previous = now;
// 这里引入了一个remaining的概念:还剩多长时间执行事件
var remaining = wait - (now - previous);
context = this;
args = arguments;
// remaining <= 0 考虑到事件停止后重新触发或者
// 正好相差wait的时候,这些情况下,会立即触发事件
// remaining > wait 没有考虑到相应场景
// 因为now-previous永远都是正值,且不为0,那么
// remaining就会一直比wait小,没有大于wait的情况
// 估计是保险起见吧,这种情况也是立即执行
if (remaining <= 0 || remaining > wait)
{
if (timeout)
{
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
// 是否跟踪
} else if (!timeout && options.trailing !== false)
{
timeout = setTimeout(later, remaining);
}
return result;
};};

由上可见,underscore考虑了比较多的情况:options.leading:

第一次是否执行,默认为true,表示第一次会执行,传入{leading:false}则禁用第一次执行options.trailing:最后一次是否执行,默认为true,表示最后一次会执行,传入{trailing: false}表示最后一次不执行所谓第一次是否执行,是刚开始触发事件时,要不要先触发事件,如果要,则previous=0,remaining 为负值,则立即调用了函数所谓最后一次是否执行,是事件结束后,最后一次触发了此方法,如果要执行,则设置定时器,即事件结束以后还要在执行一次。remianing > wait 表示客户端时间被修改过。

2. _.debounce函数

_.debounce = function(func, wait, immediate) {
// immediate默认为false
var timeout, args, context, timestamp, result;
var later = function() {
// 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func var last = _.now() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function()
{
context = this;
args = arguments;
timestamp = _.now();
// 第一次调用该方法时,且immediate为true,则调用func函数
var callNow = immediate && !timeout; // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};};

_.debounce实现的精彩之处我认为是通过递归启动计时器来代替通过调用clearTimeout来调整调用func函数的延时执行。

以上所述是小编给大家介绍的JavaScript性能优化之函数节流(throttle)与函数去抖(debounce),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • JavaScript 函数节流详解及方法总结

    JavaScript 函数节流详解 浏览器一个网页的UI线程只有一个,他同时会处理界面的渲染和页面JavaScript代码的执行(简单扩展一下,浏览器或者JavaScript运行大环境并不是单线程,诸如ajax异步回调.hybrid框架内与native通信.事件队列.CSS运行线程等等都属于多线程环境,不过ES6引入了Promise类来减少了部分异步情况).因此当JavaScript代码运行计算量很大的方法时,就有可能阻塞UI线程,小则导致用户响应卡顿,严重的情况下浏览器会提示页面无响应是否强制

  • JavaScript函数节流概念与用法实例详解

    本文实例讲述了JavaScript函数节流概念与用法.分享给大家供大家参考,具体如下: 最近在做网页的时候有个需求,就是浏览器窗口改变的时候需要改一些页面元素大小,于是乎很自然的想到了window的resize事件,于是乎我是这么写的 <!DOCTYPE html> <html> <head> <title>Throttle</title> </head> <body> <script type="text

  • JavaScript函数节流的两种写法

    最近看了函数节流的相关内容,具体什么是节流我不讨论了,网上很多,这里总结下两个方法,我只认可其中一个.另一个貌似也能达到节流的效果但是感觉不是很正规. 方法一: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>函数节流</title> <style> div{ font-size:40px;

  • 浅谈JavaScript函数节流

    浏览器中某些计算和处理要比其他的昂贵的多.例如,DOM操作比起非DOM交互需要更多的内存和CPU时间.连续尝试进行过多的DOM相关操作可能会导致 浏览器挂起,有时候甚至会崩溃.尤其在IE中使用onresize事件处理程序的时候容易发生,当调整浏览器大小的时候,该事件连续触发.在 onresize事件处理程序内部如果尝试进行DOM操作,其高频率的更改可能会让浏览器崩溃. 函数节流背后的基本思想是,某些代码不可以在没有间断的情况连续重复执行.第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代

  • JavaScript触发onScroll事件的函数节流详解

    问题描述 常见的网站布局,顶部一个导航栏,我们假设本页面共有四个栏目:分别为A.B.C.D,我们点击A,锚点跳转至A栏目,同时顶部的A按钮高亮:点击B,锚点跳转至B栏目,同时顶部的B按钮高亮:我们在Main组件里面滚动,滚动到B模块时,B按钮高亮.以上是我们经常会在开发中遇到的一个模型.如果是在以前,用jQuery作前端开发的话,实在是太熟悉不过了. 解决方案 主要想谈谈在React组件化开发中的性能优化方法. 我们的页面结构是这样的 <div className={style.main} id

  • JS中setTimeout的巧妙用法前端函数节流

    什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等等事情,那么这时候窗口缩放的时候,有可能连续发多个请求,这并不是我们想要的,或者是说我们常见的鼠标移入移出tab切换效果,有时候连续且移动的很快的时候,会有闪烁的效果,这时候我们就可以使用函数节流来操作.大家都知道,DOM的操作会很消耗或影响性能的,如果是说在窗口缩放的时候,为元素绑定大量的dom操作的话,会引发大量的连续计算,比如

  • JavaScript函数节流和函数防抖之间的区别

    一.概念解释 函数节流和函数防抖,两者都是优化高频率执行js代码的一种手段. 大家大概都知道旧款电视机的工作原理,就是一行行得扫描出色彩到屏幕上,然后组成一张张图片.由于肉眼只能分辨出一定频率的变化,当高频率的扫描,人类是感觉不出来的.反而形成一种视觉效果,就是一张图.就像高速旋转的风扇,你看不到扇叶,只看到了一个圆一样. 同理,可以类推到js代码.在一定时间内,代码执行的次数不一定要非常多.达到一定频率就足够了.因为跑得越多,带来的效果也是一样.倒不如,把js代码的执行次数控制在合理的范围.既

  • JavaScript性能优化之函数节流(throttle)与函数去抖(debounce)

    函数节流,简单地讲,就是让一个函数无法在很短的时间间隔内连续调用,只有当上一次函数执行后过了你规定的时间间隔,才能进行下一次该函数的调用. 函数节流的原理挺简单的,估计大家都想到了,那就是定时器.当我触发一个时间时,先setTimout让这个事件延迟一会再执行,如果在这个时间间隔内又触发了事件,那我们就clear掉原来的定时器,再setTimeout一个新的定时器延迟一会执行,就这样. 以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1.

  • javascript性能优化之事件委托实例详解

    本文实例分析了javascript性能优化之事件委托.分享给大家供大家参考,具体如下: 为下面每个LI绑定一个click事件 <ul id="myLinks"> <li id="goSomewhere" >Go somewhere</li> <li id="doSomething" >Do something</li> <li id="sayHi" >Sa

  • JavaScript性能优化总结之加载与执行

    前言 无论当前 JavaScript 代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成.JavaScript 执行过程耗时越久,浏览器等待响应用户输入的时间就越长.浏览器在下载和执行脚本时出现阻塞的原因在于,脚本可能会改变页面或 JavaScript 的命名空间,它们对后面页面内容造成影响.一个典型的例子就是在页面中使用document.write(),例如清单 1 清单 1 JavaScript 代码内嵌示例 <html> <head> <title

  • 基于JavaScript 性能优化技巧心得(分享)

    JavaScript 作为当前最为常见的直译式脚本语言,已经广泛应用于 Web 应用开发中.为了提高Web应用的性能,从 JavaScript 的性能优化方向入手,会是一个很好的选择. 本文从加载.上下文.解析.编译.执行和捆绑等多个方面来讲解 JavaScript 的性能优化技巧,以便让更多的前端开发人员掌握这方面知识. 什么是高性能的 JavaScript 代码? 尽管目前没有高性能代码的绝对定义,但却存在一个以用户为中心的性能模型,可以用作参考:RAIL模型. 响应 如果你的应用程序能在1

  • JavaScript 性能优化小结

    随着 Web2.0 技术的不断推广,越来越多的应用使用 JavaScript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 JavaScript 的阻塞特性变的复杂,也就是说当浏览器在执行 JavaScript 代码时,不能同时做其他任何事情.本文详细介绍了如何正确的加载和执行 JavaScript 代码,从而提高其在浏览器中的性能. 在J2EE编程中,我们接触最多的脚本语言还是JavaScript.在使用JavaScr

  • javascript性能优化之DOM交互操作实例分析

    本文实例讲述了javascript性能优化之DOM交互操作技巧.分享给大家供大家参考,具体如下: 在javascript各个方面,DOM毫无疑问是最慢的一部分.DOM操作与交互要耗费大量时间,因为它们往往需要重新渲染整个页面或者某一部分.理解如何优化与DOM的交互可以极大提高脚本完成的速度. 1.最小化DOM更新 看下面例子: var list = document.getElementById("ul"); for (var i=0; i < 10; i++){ var ite

  • javascript性能优化之分时函数的介绍

    分时函数和函数节流的问题不一样,函数节流针对的事件不是用户主动调用的,前面已经提过了. 函数节流的原理是:延迟当前函数的执行,如果该次延迟还没有完成,那么忽略接下来该函数的请求.也就是说会忽略掉很多函数请求. 在一些开发场景中,我们可能会一次性向文档中注入上千个节点,在短时间内向浏览器中大量添加DOM节点可能会让浏览器吃不消,结果往往会让浏览器卡顿或吃不消,解决方案之一便是使用分时函数(timeChunk). timeChunk分时函数让创建节点的工作分批进行,比如一秒钟创建1000个节点,改为

  • JavaScript性能优化 创建文档碎片(document.createDocumentFragment)

    在浏览器中,我们一旦把节点添加到document.body(或者其他节点)中,页面就会更新并反映出这个变化,对于少量的更新,一条条循环插入也会运行很好,也是我们常用的方法.代码如下: 复制代码 代码如下: for(var i=0;i<5;i++){ var op = document.createElement("span"); var oText = document.createTextNode(i); op.appendChild(oText); document.body

  • JavaScript性能优化之小知识总结

    随着网络的发展,网速和机器速度的提高,越来越多的网站用到了丰富客户端技术.而现在Ajax则是最为流行的一种方式.JavaScript是一种解释型语言,所以能无法达到和C/Java之类的水平,限制了它能在客户端所做的事情,为了能改进他的性能,我想基于我以前给JavaScript做过的很多测试来谈谈自己的经验,希望能帮助大家改进自己的JavaScript脚本性能. 前言 一直在学习javascript,也有看过<犀利开发Jquery内核详解与实践>,对这本书的评价只有两个字犀利,可能是对javas

随机推荐