JS防抖与节流(类比游戏技能)
2023-06-13 09:15:17 时间
JS防抖与节流(类比游戏技能)
防抖
生活中防抖的例子
- 酒店的自动开关门
- 感应到人自动开门,5s后自动关闭
- 如果5s的倒计时里有人来,那么这个5s重新开始计时
类比技能吟唱
- 知道释放一个法术(魔法)是需要吟唱时间的
- 比如死歌的R
- 比如波比的R
- 假设的技能没有CD
- 那么在技能还没有吟唱完的时候再一次使用技能会打断第一次施法,重新开始施法
- 在上次技能没释放的时候释放下一次技能,第一次施法被打断是很好理解的吧
- 重新施法,重新开始吟唱也是很好理解的吧
js中的防抖
- 如键盘输入等
就和上面两个例子中提到的一样。 主要是用到了三点
- addEventListener
- setTimeout
- clearTimeout
基础的html代码如下
const button = document.querySelector('#input')
function input () {
console.log('input!');
}
function debounce (fn,delay) {
// Todo: 完成防抖
}
button.addEventListener('click', debounce(input,1000))
- 因为防抖需要知道延时时间和完成的事件,所以防抖有两个参数
1.使用高阶函数,避免添加响应的时候触发函数
- 一步一步来,如果没有使用高阶函数而是直接调用函数,如下图,会在添加响应的时候触发fn()
function debounce (fn,delay) {
fn()
}
所以先改为高阶函数解决这个bug
function debounce (fn,delay) {
return function () {
fn()
}
}
2.使用setTimeout完成延时执行,clearTimeout来控制刷新延时
2.1 为什么要把timer定义在函数外面?
为了形成闭包,这样在函数内部才能保证clearTimeout掉timer
function debounce (fn, delay) {
let timer
return function () {
clearTimeout(timer)
timer = setTimeout(function () {
fn()
}, delay)
}
}
3.并不是万事大吉,注意this指针的改变
在函数里面log一下this指针
const button = document.querySelector('#input')
function input () {
console.log('input!');
console.log(this);
}
function debounce (fn, delay) {
let timer
return function () {
clearTimeout(timer)
timer = setTimeout(function () {
fn()
}, delay)
}
}
button.addEventListener('click', debounce(input, 300))
结果函数内部的this指针变成了window,这是肯定不行的
3.1 用context保存this指针,args保存添加的参数,用apply来改变this指针的指向
function debounce (fn, delay) {
let timer
return function () {
let context = this
let args=arguments
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context,args)
}, delay)
}
}
成功得到input的this指针
4.return的function不可以使用匿名函数
其实是可以用匿名函数来简化上面的代码的,但是只能简化setTimeout
里面的函数
- 需要拿到
function
里面的context上下文 - 因为匿名函数没有
this
指针,所以return
的函数写成匿名函数会出错,代码如下
function debounce (fn, delay) {
let timer
return () => {
let context = this
let args=arguments
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context,args)
}, delay)
}
}
这种写法,this
指针会指向window
最终代码
function debounce (fn, delay) {
let timer
return function () {
let context = this
let args = arguments
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
节流
生活中节流的例子
- 鲸鱼到水面换气
- 鲸鱼是哺乳动物,所以需要每隔一段时间到水面换气
- 不一直在水面,因为还需要下潜保持皮肤的水分,这就和节约资源类似
- 鲸鱼呷一口气,这口气能支撑1个小时,那鲸鱼就下潜了,1小时后再来换气
- 这就是节流
类比平A僵直
打过王者荣耀的都知道吧,拆塔的时候疯狂按攻击键?这没多大用
你的攻击次数由你的攻击速度(攻击间隔时长)决定
就是在上一次攻击指令完成前,是没有办法进行下一次攻击指令的
平A的僵直就是节流
js中的节流
- 如滚动监听
在上面防抖基础上,节流的介绍就不再那么啰嗦,直接开始编写节流的代码
1.使用定时器的思想来完成
1.1根据思路写出基础代码
- 同样是高阶函数
- 同样要操作timer,timer闭包
- 如果上一次还没延时没结束,就啥也不做(判断timer是否存在即可)。完成节流任务
function throttle (fn, delay) {
let timer
return function () {
if (timer) return
timer = setTimeout(() => {
fn()
timer = null
}, delay)
}
}
1.2关键点还是改变this的指向
function throttle (fn, delay) {
let timer
return function () {
let context = this
let args = arguments
if (timer) return
timer = setTimeout(() => {
fn.apply(context, args)
timer = null
}, delay)
}
}
2.使用Date对象来实现
2.1综合前面的问题,直接写出来
- 但是发现有一个问题,就是函数第一次会立即执行
function throttle (fn, delay) {
let pre = 0
return function () {
let now = new Date()
let context = this
let args = arguments
if (now - pre > delay) {
fn.apply(context, args)
pre = now
}
}
}
2.2可以第一次不立即执行吗?可以!
判断,如果!pre就是第一次,第一次先拿到当前时间就可以了
function throttle (fn, delay) {
let pre = 0
return function () {
if (!pre) pre = new Date()
let now = new Date()
let context = this
let args = arguments
if (now - pre > delay) {
fn.apply(context, args)
pre = now
}
}
}
3.讨论一下前两种方案的弊端
- 假设有两次请求,第一次正常,第二次被节流了
- 最后只执行了一次,这是不愿意看到的!
- 如何解决?请看第4点
4.Date结合定时器解决3.的弊端
- 在2中代码的基础上来添加
- 闭包的timer,如果在节流时间内,就定一个定时器来完成被吃掉的请求
- 定时为剩下的时间remainTime=delay-(now-pre),定时前记得判断是否存在(存在就不用设置直接return就可以了),完成后记得要更新pre和timer=null
function throttle (fn, delay) {
let pre = 0
let timer
return function () {
if (!pre) pre = new Date()
let now = new Date()
let context = this
let args = arguments
let remainTime = delay - (now - pre)
if (now - pre > delay) {
fn.apply(context, args)
pre = now
} else {
if (timer) return
timer = setTimeout(() => {
fn.apply(context, args)
pre = now
timer = null
}, remainTime)
}
}
}
相关文章
- js面试题及答案2020_JS面试题大全
- JS跳转代码_js中跳转页面路径
- 基于js原生算法+cocos游戏引擎+uni框架Cloud托管网页:开发2048小游戏域名发布版本
- 【说站】js策略模式是什么?
- js定时器与延时器_JS做定时器倒计时
- Js生成二维码_js在线生成二维码
- js:如何获取select选中的值
- js对象和原型、原型链的关系_2023-03-02
- 【JS 逆向百例】猿人学系列 web 比赛第五题:js 混淆 - 乱码增强,详细剖析
- 【JS 逆向百例】猿人学系列 web 比赛第二题:js 混淆 - 动态 cookie,详细剖析
- clipboard.js:最轻便的复制页面内容到剪切板的JS
- 一些元素位置设置的通用JS方法详解编程语言
- node.js:exopress-连接mysql报错:Access denied for user ‘root’@’localhost’ (using password: NO)问题解决。详解编程语言
- 使用JS实现文字搬运工详解编程语言
- JS createAttribute()方法:创建元素节点
- 使用JS连接MySQL数据库:实现化繁为简(js连接mysql数据库)
- 快速掌握JS操作MySQL数据库技巧(js操作mysql数据库)
- 利用 JS 实现 Redis 的连接(js连接redis)
- JS实时链接Oracle让数据库访问更便捷(js实时连接oracle)
- 准确获得页面、窗口高度及宽度的JS
- js对象之JS入门之Array对象操作小结
- 用JS控制回车事件的代码
- js常用代码段收集
- 远离JS灾难css灾难之js私有函数和css选择器作为容器
- js写一个字符串转成驼峰的实例
- js模仿hover的具体实现代码
- Node.js制作实时多人游戏框架