“浅入浅出”函数防抖(debounce)与节流(throttle)
函数防抖与节流是日常开发中经常用到的技巧,也是前端面试中的常客,但是发现自己工作一年多了,要么直接复用已有的代码或工具,要么抄袭《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)的更多相关文章
- 防抖debounce和节流throttle
大纲 一.出现缘由 二.什么是防抖debounce和节流throttle 三.应用场景 3.1防抖 3.2节流 一.出现缘由 前端开发中,有一部分用户行为会频繁触发事件,而对于DOM操作,资源加载等耗 ...
- js 函数的防抖(debounce)与节流(throttle)
原文:函数防抖和节流: 序言: 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频 ...
- js 函数的防抖(debounce)与节流(throttle) 带 插件完整解析版 [helpers.js]
前言: 本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽. 函数防抖与节流是做什么的?下面进行通俗的讲解. 本文借鉴:h ...
- Java版的防抖(debounce)和节流(throttle)
概念 防抖(debounce) 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时. 防抖,即如果短时间内大量触发同一事件,都会 ...
- 防抖(Debounce)与节流( throttle)区别
http://www.cnblogs.com/ShadowLoki/p/3712048.html http://blog.csdn.net/tina_ttl/article/details/51830 ...
- js 防抖 debounce 与 节流 throttle
debounce(防抖) 与 throttle(节流) 主要是用于用户交互处理过程中的性能优化.都是为了避免在短时间内重复触发(比如scrollTop等导致的回流.http请求等)导致的资源浪费问题. ...
- JavaScript 防抖(debounce)和节流(throttle)
防抖函数 触发高频事件后,n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 /** * * @param {*} fn :callback function * @param {* ...
- 浅入浅出EmguCv(一)OpenCv与EmguCv
最近接触计算机视觉方面的东西,于是准备下手学习opencv,从官网下载windows的安装版,配置环境,一系列步骤走完后,准备按照惯例弄个HelloWord.也就是按照网上的教程,打开了那个图像处理领 ...
- 浅入深出之Java集合框架(中)
Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- 包学会之浅入浅出Vue.js:结业篇(转)
蔡述雄,现腾讯用户体验设计部QQ空间高级UI工程师.智图图片优化系统首席工程师,曾参与<众妙之门>书籍的翻译工作.目前专注前端图片优化与新技术的探研. 在第一篇<包学会之浅入浅出Vu ...
随机推荐
- echart在X轴下方添加字
使用Echart做统计图表,这个方便快捷还高大上 官方网址 https://www.echartsjs.com/ 按照文档,很快就做出了一个柱图表 在X轴下方,要显示出对应日期是星期几(上图最下方,用 ...
- DataReader转Dictionary数据类型之妙用
datareader转dictionary有很多用处,可以输出表中部分字段转实体字段,以前需要全部字段输出或者再建一个实体模型才行,这样就可以减少数据库的输出量了,特别是某些接口的格式化输出很方便. ...
- setTimeout与setInterval
setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式. 语法:setTimeout(code/function, milliseconds, param1, param2, ...) ...
- 获取windows凭证管理器明文密码
1.运行cmdkey /list查看windows保存凭证 方法1.mimikaz mimikatz vault::cred 2.利用powershell尝试获取 windows 普通凭据类型中的明文 ...
- PLsql快捷键
plsql使用技巧 1.类SQL PLUS窗口:File->New->Command Window,这个类似于oracle的客户端工具sql plus,但比它好用多了. 2.设置关键字自动 ...
- 常用Hadoop命令(bin)
**** bin 是二进制文件的意思,sbin....据说是superbin(管理员的bin) HDFS命令 某个文件的blocks信息 hadoop fsck /user/xx -files -bl ...
- Java发布webservice应用并发送SOAP请求调用
webservice框架有很多,比如axis.axis2.cxf.xFire等等,做服务端和做客户端都可行,个人感觉使用这些框架的好处是减少了对于接口信息的解析,最主要的是减少了对于传递于网络中XML ...
- springMVC中数据流解析与装载
最近在看springmvc原理时,看到一篇比较赞的博文,留存学习,如果侵权,请告知,立删. 地址: https://my.oschina.net/lichhao/blog/172562
- Oracle 11g 服务启动/关闭 及 DB dump 导入
本地启动Oracle 服务脚本 由于本地机子安装了Oracle后,会自动启动一些默认的Oracle服务,这样子会导致机子比较慢.所以需要改成手动启动/关闭服务. 即用即开,不用就关. 开启的脚本: O ...
- MYSQL数据库的设计与调优
优化思路: 1.检查数据表结构,改善不完善设计 2.跑一遍主要业务,收集常用的数据库查询SQL 3.分析查询SQL,适当拆分,添加索引等优化查询 4.优化SQL的同时,优化代码逻辑 5.添加本地缓存和 ...