函数防抖与节流是日常开发中经常用到的技巧,也是前端面试中的常客,但是发现自己工作一年多了,要么直接复用已有的代码或工具,要么抄袭《JS高级程序设计》书中所述“函数节流”,(实际上红宝书上的实现类似是函数防抖而不是函数节流),还没有认真的总结和亲自实现这两个方法,实在是一件蛮丢脸的事。网上关于这方面的资料简直就像是中国知网上的“水论文”,又多又杂,难觅精品,当然,本文也是一篇很水的文章,只当是个人理解顺便备忘,毕竟年纪大了,记忆力下降严重。CSS-Tricks上这篇文章Debouncing and Throttling Explained Through Examples算是非常通识的博文,值得一读。

函数防抖与节流的区别及应用场合

关于函数常规、防抖、节流三种执行方式的区别可以通过下面的例子直观的看出来

函数防抖和节流都能控制一段时间内函数执行的次数,简单的说,它们之间的区别及应用:

  • 函数防抖: 将本来短时间内爆发的一组事件组合成单个事件来触发。等电梯就是一个非常形象的比喻,电梯不会立即上行,而是等待一段时间内没有人再上电梯了才上行,换句话说此时函数执行时一阵一阵的,如果一直有人上电梯,电梯就永远不会上行。

使用场合:用户输入关键词实时搜索,如果用户每输入一个字符就发请求搜索一次,就太浪费网络,页面性能也差;再比如缩放浏览器窗口事件;再再比如页面滚动埋点

  • 函数节流: 控制持续快速触发的一系列事件每隔'X'毫秒执行一次,就像Magic把瓢泼大雨编程了绵绵细雨。

使用场合:页面滚动过程中不断统计离底部距离以便懒加载。

函数防抖与节流的简易实现

如果应用场合比较常规,根据上述函数防抖和节流的概念,代码实现还是比较简单的:
简易防抖工具函数实现如下:

function debounce(func, wait) {
let timerId
return function(...args) {
timerId && clearTimeout(timerId)
timerId = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}

防抖高阶函数实现很简单,瞄一眼就懂,但是仍要注意:代码第三行返回的函数并没有使用箭头函数,目的是在事件执行时确定上下文,节流的高阶函数实现起来相对复杂一点。

function throttle(func, wait = 100) {
let timerId
let start = Date.now()
return function(...args) {
const curr = Date.now()
clearTimeout(timerId)
if (curr - start >= wait) {// 可以保证func一定会被执行
func.apply(this, args)
start = curr
} else {
timerId = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
}

Lodash函数防抖(debounce)与节流(throttle)源码精读

上面的基本实现大致满足绝大多数场景的需求,但是Lodash库中的实现则更加完备,下面我们一起看看其源码实现。

import isObject from "./isObject.js"
import root from "./.internal/root.js" function debounce(func, wait, options) {
/**
* maxWait 最长等待执行时间
* lastCallTime 事件上次触发的时间,由于函数防抖,真正的事件处理程序并不一定会执行
*/
let lastArgs, lastThis, maxWait, result, timerId, lastCallTime let lastInvokeTime = 0 // 上一次函数真正调用的时间戳
let leading = false // 是否在等待时间的起始端触发函数调用
let maxing = false //
let trailing = true // 是否在等待时间的结束端触发函数调用 // 如果没有传入wait参数,检测requestAnimationFrame方法是否可以,以便后面代替setTimeout,默认等待时间约16ms
const useRAF =
!wait && wait !== 0 && typeof root.requestAnimationFrame === "function" if (typeof func != "function") {
// 必须传入函数
throw new TypeError("Expected a function")
}
wait = +wait || 0 // wait参数转换成数字,或设置默认值0
if (isObject(options)) {
// 规范化参数
leading = !!options.leading
maxing = "maxWait" in options
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
trailing = "trailing" in options ? !!options.trailing : trailing
}
// 调用真正的函数,入参是调用函数时间戳
function invokeFunc(time) {
const args = lastArgs
const thisArg = lastThis lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}
// 开启计时器方法,返回定时器id
function startTimer(pendingFunc, wait) {
if (useRAF) {
// 如果没有传入wait参数,约16ms后执行
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}
//等待时间起始端调用事件处理程序
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time
// Start the timer for the trailing edge.
timerId = startTimer(timerExpired, wait)
// Invoke the leading edge.
return leading ? invokeFunc(time) : result
} function remainingWait(time) {
// 事件上次触发到现在的经历的时间
const timeSinceLastCall = time - lastCallTime
// 事件处理函数上次真正执行到现在经历的时间
const timeSinceLastInvoke = time - lastInvokeTime
// 等待触发的时间
const timeWaiting = wait - timeSinceLastCall
// 如果用户设置了最长等待时间,则需要取最小值
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting
}
// 判断某个时刻是否允许调用真正的事件处理程序
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
return (
lastCallTime === undefined || // 如果是第一次调用,则一定允许
timeSinceLastCall >= wait || // 等待时间超过设置的时间
timeSinceLastCall < 0 || // 当前时刻早于上次事件触发时间,比如说调整了系统时间
(maxing && timeSinceLastInvoke >= maxWait) // 等待时间超过最大等待时间
)
}
// 计时器时间到期执行的回调
function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// 重新启动计时器
timerId = startTimer(timerExpired, remainingWait(time))
} function trailingEdge(time) {
timerId = undefined // 只有当事件至少发生过一次且配置了末端触发才调用真正的事件处理程序,
// 意思是如果程序设置了末端触发,且没有设置最大等待时间,但是事件自始至终只触发了一次,则真正的事件处理程序永远不会执行
if (trailing && lastArgs) {
return invokeFunc(time)
}
lastArgs = lastThis = undefined
return result
}
// 取消执行
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId)
}
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}
// 立即触发一次事件处理程序调用
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}
// 查询是否处于等待执行中
function pending() {
return timerId !== undefined
} function debounced(...args) {
const time = Date.now()
const isInvoking = shouldInvoke(time) lastArgs = args
lastThis = this
lastCallTime = time if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime)
}
if (maxing) {
// Handle invocations in a tight loop.
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}
export default debounce

Lodash中throttle直接使用debounce实现,说明节流可以当作防抖的一种特殊情况。

function throttle(func, wait, options) {
var leading = true,
trailing = true; if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
'leading': leading,
'maxWait': wait,
'trailing': trailing
});
}

“浅入浅出”函数防抖(debounce)与节流(throttle)的更多相关文章

  1. 防抖debounce和节流throttle

    大纲 一.出现缘由 二.什么是防抖debounce和节流throttle 三.应用场景 3.1防抖 3.2节流 一.出现缘由 前端开发中,有一部分用户行为会频繁触发事件,而对于DOM操作,资源加载等耗 ...

  2. js 函数的防抖(debounce)与节流(throttle)

    原文:函数防抖和节流: 序言: 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频 ...

  3. js 函数的防抖(debounce)与节流(throttle) 带 插件完整解析版 [helpers.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         函数防抖与节流是做什么的?下面进行通俗的讲解. 本文借鉴:h ...

  4. Java版的防抖(debounce)和节流(throttle)

    概念 防抖(debounce) 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时. 防抖,即如果短时间内大量触发同一事件,都会 ...

  5. 防抖(Debounce)与节流( throttle)区别

    http://www.cnblogs.com/ShadowLoki/p/3712048.html http://blog.csdn.net/tina_ttl/article/details/51830 ...

  6. js 防抖 debounce 与 节流 throttle

    debounce(防抖) 与 throttle(节流) 主要是用于用户交互处理过程中的性能优化.都是为了避免在短时间内重复触发(比如scrollTop等导致的回流.http请求等)导致的资源浪费问题. ...

  7. JavaScript 防抖(debounce)和节流(throttle)

    防抖函数 触发高频事件后,n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 /** * * @param {*} fn :callback function * @param {* ...

  8. 浅入浅出EmguCv(一)OpenCv与EmguCv

    最近接触计算机视觉方面的东西,于是准备下手学习opencv,从官网下载windows的安装版,配置环境,一系列步骤走完后,准备按照惯例弄个HelloWord.也就是按照网上的教程,打开了那个图像处理领 ...

  9. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  10. 包学会之浅入浅出Vue.js:结业篇(转)

    蔡述雄,现腾讯用户体验设计部QQ空间高级UI工程师.智图图片优化系统首席工程师,曾参与<众妙之门>书籍的翻译工作.目前专注前端图片优化与新技术的探研. 在第一篇<包学会之浅入浅出Vu ...

随机推荐

  1. jq的on click 事件在苹果下无效

    据说苹果对于点击的对象,拥有cursor:pointer这个样式的设置才算 参考地址:https://blog.csdn.net/yuexiage1/article/details/51612496

  2. Python--基础二

    Python基础:字符串str & 列表list & 元组tuple & 字典dict & 集合set Python 基础 2 字符串 str 字符串是以单引号或双引号 ...

  3. Keepalived配置详解

    Keepalived 配置文件解释 Keepalived的所有配置都在一个配置文件里面,主要分为三类: 全局配置 VRRPD配置 LVS 配置 配置文件是以配置块的形式存在,每个配置块都在一个闭合的{ ...

  4. 纠错式教学法对比鼓励式教学法 -----Lily、贝乐、英孚,乐加乐、剑桥国际、优学汇、北外青少

    一.关于两种英语教学法的争议 在英语教学方面,主要有纠错式教学法(目前主要对应国内听说读写四位一体的教学法)和鼓励式教学法(目前对应国内听说为主的教学法),这两种教学方法其实是各有千秋,各有利弊的. ...

  5. Python爬虫之三

    1)使用Scrapy,什么叫做Scrapy Scrapy,Python开发的一个快速.高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据.Scrapy用途广泛,可以用于数据 ...

  6. VS配置Halcon(一次配置,永久使用)

    [说明]只需配置一次,以后新项目无需再次配置. 本教程是64位版本,32位可参考本教程.VS与Halcon无论哪个版本,都可参考本教程. [步骤]以VS2015+Halcon18.11为例 1.新建一 ...

  7. python-tqdm进度条

    通过tqdm库可以打造自己的进度条使用 1. pip安装 pip install tqdm --trusted-host pypi.douban.com 2.使用 from time import s ...

  8. TF用法指导

    一.变量 1 .tf.Variables() 功能:创建变量 2.tf.get_variable() 功能:创建变量,注意和上面的区别 3.tf.variable_scope() 功能:其实只要记住一 ...

  9. 微信小程序 开发文档

    官方开发文档: 小程序公众平台 小程序开发者指南 小程序开发者文档 学习资源: 微信:官方入门教程 微信:WeUI 是一套同微信原生视觉体验一致的基础样式库 微信:微信小程序示例 视频: 学堂在线:学 ...

  10. java ,用公司框架写的显示列表 Table控件

    <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" lan ...