文章背景:
- 事件防抖引发的对事件循环机制的思考,部分异步放在闭包外部的执行顺序
- 代码实战:React 事件处理函数中 onClick = 直接调用函数 "this.fn()",或在调用时使用 bind绑定this "this.fn.bind(this)"、或在调用的时候使用箭头函数绑定this "() => this.handleClick()"三者区别
先说结论
防抖中,部分异步放在闭包外面,会导致结果如下:
...
// 部分异步不防抖
_this.props.dispatch({
type: "test/debounce",
param
})
.then(() => {
_this.editData()
})
// 部分异步不防抖 end
.then(() => {
return function () {
console.log('---第一次 timeout: ', timeout) // 2 2
clearTimeout(timeout); //每次触发input值改变时,首先清空上一次的setTimeout
console.log('---第二次 timeout: ', timeout); // 3 3
timeout = setTimeout(() => { // 4 4
_this.updateAge() // 重新获取页面数据
console.log('---我是setTimeout, 防抖清除函数们', "---timeout的值: ", timeout)
}, 1000) // 注意的是如果你写的代码用有用到this,要提前声明this是谁,因为在箭头函数中没有this指向
console.log('---debounce中的闭包执行完毕', timeout)
}()
})
...
- 先去执行完同步代码(即两次点击后),setTimeout最后才会去执行;
- 由于clearTimeout时,setTimeout并未执行,timeout并没有定义上setTimeout,所以其实并未清除 this.updateAge
- setTimeout 最终执行的次数即为点击的次数
作者:林轩
链接:juejin.cn/post/698919…
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
写法一 —— 正确写法
第一步:render 中的定义click方法
- 下方代码中方式一:不可行;
因为bind是改变this,返回的一个函数,会将 函数debounce 改变下this,直接原封不动的返回;
而我们要的是 debounce()执行一下,需要让 onClick = debounce(),返回一个闭包,最终我们执行的是 debounce中的返回的函数
- 下方代码中方式二:可行;
debounce(),返回的是里面定义的闭包;
render时,会执行 debounce(),不过没啥太大关系,我们主要执行的是 debounce()里返回的函数,并且 debounce函数定义前面没太多与逻辑相关的代码,不会出错的
// render
// 方式一
{/* <span className={styles.left} onClick={this.debounce.bind(this, 'subtract')}>-</span> */}
// 方式二
<span className={styles.left} onClick={this.debounce('subtract')}>-</span>
第二步:定义debounce 事件
- 方法一:把所有函数写到setTimeout里面,可行
正确合理使用闭包,清除函数是要清除所有的函数,而不能在外边 —— 例写法二中的debounce 定义
debounce = (option) => {
console.log('---进入debounce方法')
let param={};
// 处理一些逻辑
if (option === 'str') {
param = {a:1}
} else {
param = {a:1}
}
const _this = this;
let timeout = null;
return function () {
console.log('---第一次 timeout: ', timeout)
clearTimeout(timeout);
console.log('---第二次 timeout: ', timeout)
timeout = setTimeout(() => { // 4 4
**// 防抖的函数们**
_this.props.dispatch({
type: "test/debounce",
param
})
.then(() => {
_this.editData()
})
.then(() => {
_this.updateAge()
})
**// 防抖的函数们 end**
console.log('---我是setTimeout, 防抖清除函数们', "---timeout的值: ", timeout)
}, 500)
console.log('---debounce中的闭包执行完毕', timeout)
}
}
- 下方为快速点击两次按钮时,执行顺序如下:
写法二 —— 错误写法(部分异步写到闭包外面,只想清除部分函数抖动)
第一步:render定义
-
由于写法二的错误写法,是把部分异步写到闭包外面,只想清除部分函数抖动,
-
render时,debounce() 中的异步们就会执行,可能某些数据还会更新好,render时就执行异步可能会导致报错
-
bind(),表示改变this,返回一个函数体,而不是执行函数;就不会导致render时,debounce执行了,而是onClick的时候去执行 debounce
-
只能如下写,两种写法,这两种写法区别详见:react.docschina.org/docs/handli…
<span className={styles.left} onClick={this.debounce.bind(this, 'subtract')}>-</span>
第二步:定义 debounce
-
把部分异步写到闭包外面,不防抖;只想清除部分函数抖动,
-
执行结果看下图,原因见下方的 "结论"
// 加减年龄
debounce = (option) => {
console.log('---进入debounce方法')
let param={};
// 处理一些逻辑
if (option === 'str') {
param = {a:1}
} else {
param = {a:1}
}
const _this = this;
let timeout = null;
// 部分异步不防抖
_this.props.dispatch({
type: "test/debounce",
param
})
.then(() => {
_this.editData()
})
// 部分异步不防抖 end
.then(() => {
return function () {
console.log('---第一次 timeout: ', timeout) // 2 2
clearTimeout(timeout);
console.log('---第二次 timeout: ', timeout); // 3 3
timeout = setTimeout(() => { // 4 4
_this.updateAge()
console.log('---我是setTimeout, 防抖清除函数们', "---timeout的值: ", timeout)
}, 1000)
console.log('---debounce中的闭包执行完毕', timeout)
}()
})
}
错误写法执行结果的原因
-
因为事件循环机制是先 主任务 —> 微任务 —> 宏任务,setTimeout 为宏任务,将 需要在下一个时间周期执行
-
事件循环机制流程图如下:
- 详解如下:
2 同步任务就继续执行,一直执行完 3 遇到异步API就将它交给对应的异步线程,自己继续执行同步任务 4 异步线程执行异步API,执行完后,将异步回调事件放入事件队列上 5 主线程手上的同步任务干完后就来事件队列看看有没有任务 6 主线程发现事件队列有任务,就取出里面的任务执行 7 主线程不断循环上述流程
定时器不准
Event Loop的这个流程里面其实还是隐藏了一些坑的,最典型的问题就是总是先执行同步任务,然后再执行事件队列里面的回调。这个特性就直接影响了定时器的执行,我们想想我们开始那个2秒定时器的执行流程:
2 遇到setTimeout,将它交给定时器线程 3 定时器线程开始计时,2秒到了通知事件触发线程 4 事件触发线程将定时器回调放入事件队列,异步流程到此结束 5 主线程如果有空,将定时器回调拿出来执行,如果没空这个回调就一直放在队列里。
结论
上述错误写法的执行结果,结论如下:
-
先去执行完同步代码(即两次点击后),setTimeout最后才会去执行;
-
由于clearTimeout时,setTimeout并未执行,timeout并没有定义上setTimeout,所以其实并未清除 this.updateAge
-
setTimeout 最终执行的次数即为点击的次数
举个例子:原理同上
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div></div>
<input id="inp" />
<script>
inp.addEventListener('input', () => {
console.log('script start');
setTimeout(function () {
console.log('---setTimeout')
}, 5000);
Promise.resolve().then(function () {
console.log('promise1');
}).then(function () {
console.log('promise2');
})
});
</script>
</body>
</html>
打印如下:
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!