函数防抖与节流是日常开发中经常用到的技巧,也是前端面试中的常客,但是发现自己工作一年多了,要么直接复用已有的代码或工具,要么抄袭《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. Sublime Text 3 安装简记

    1.下载:( Sublime Text Version 3.1.1 Build 3176 ) https://www.sublimetext.com/3 2.安装Package Control: &q ...

  2. 定向耦合器 Directional Couplers

    microwave101,干货比较多 传送门:https://www.microwaves101.com/encyclopedias/directional-couplers Directional ...

  3. visual studio 中sstrcpy报错的问题

    项目->属性->C/C++->预处理器->预处理器定义->添加 _CRT_SECURE_NO_WARNINGS

  4. NGINX+PHP配置

    NGINX做为WEB服务器,运行PHP开发的程序和页面: server { listen 80; listen 443 ssl; ssl_certificate /usr/local/nginx/co ...

  5. Prometheus-Consul-Api

    官方地址:https://www.consul.io/docs/agent/http.html consul的主要接口是RESTful HTTP API,该API可以用来增删查改nodes.servi ...

  6. 野路子码农系列(3)plotly可视化的简单套路

    又双叒叕要跟客户汇报了,图都准备好了吗?matplotlib出图嫌丑?那用用plotly吧,让你的图看上去经费爆炸~ P1 起因 第一次接触plotly这个库是在我们做的一个列车信号数据挖掘的项目里, ...

  7. TCP/IP详解(包含ack,seq)

    前言 个人认为在web开发中,对于TCP/IP协议的理解是首当其冲的,在大多数框架的冲击下,使我们淡化了对于TCP/IP协议的理解. 理解好TCP/IP对于每个web开发者都是很有必要的. TCP/I ...

  8. windows的WSl安装mysql数据库以及操作数据库

    1.更新 sudo apt-get update sudo apt-get upgrade 2.安装mysql sudo apt-get install mysql-server 3.开启服务 sud ...

  9. centos7.4下的python3.6的安装

    1.系统环境 :centos 7.4 最小化安装 2.安装过程 yum install wget      安装下载工具 wget https://www.python.org/ftp/python/ ...

  10. 【迅为电子】迷你工控机_24小时运行_无线WIFI_超多接口

    全封闭防尘_迅为嵌入式工控主机_运行Linux-QT4.7操作系统 技术规格参数: 设备型号:eTOP-A7-MANNV10 CPU:Cortex-A7 内存:512MDDR 存储:8G EMMC 电 ...