之前写异步JS时不小心把定时器给遗漏了,作为异步函数,有时候我们优化性能,减轻页面压力需要用到定时器,然后在查阅文档时也发现了自己的一些不足,于是尽量在这篇文章上补齐,方便自己以后能回顾。
setTimeout()
当delay秒之后执行function函数
参数
- function 当定时器结束,会自动执行这个函数
- code 可以使用字符串代替函数(不推荐这种语法)
- delay 延时(单位:毫秒),如果这个参数是0,可以省略
- args 参数,这个参数会传递给function
示例:
setTimeout((...args)=>{
console.log(args)
},1000,1,2,3,4,5)
// 1秒钟后结果:[1,2,3,4,5]
// 不建议的操作
setTimeout('console.log(123)',1000) //现在谷歌已经拒绝这样的用法了
setTimeout()
执行后会返回一个id,这个结果是定时器的编号,通过这个编号我们可以取消定时器
let timer=setTimeout(()=>{console.log(123)})
clearTimeout(timer)
定时器与this
需要注意的是定时器会将函数内部的运行环境设置为全局运行环境,也就是指向window
var x= 1
var obj={
x:2,
y(){console.log(this.x)}
}
setTimeout(obj.y,1000) //2
在obj.y当成参数时,会隐式丢失函数内部的this,所以上面的打印结果为2
解决方法:
1、传递一个函数,让obj.y在函数内部执行
setTimeout(()=>{obj.y()},1000)
2、使用bind绑定
setTimeout(obj.y.bind(obj),1000)
setInterval()
这个函数跟setTimeout的用法基本一样,只不过它是隔一段时间执行,就相当于每隔一个delay执行function。
注意:这个函数是多少秒之后开始执行
,它不会管执行的时间。假设一个任务是定时100ms执行一个函数,而这个函数执行需要花费5ms,那么第一次之后定时器都会在任务执行后的95ms后执行,所以时间方便不会很精确。
所以一般我们使用setTimeout来模拟setInterval
var timer=null
let count =0
function fn(){
clearTimeout(timer)
timer = setTimeout(()=>{
count +=1
console.log(count)
fn()
},1000)
if(count ===10){
clearTimeout(timer)}
}
上面这段代码我尽可能想每一次都清除前一个定时器,目前代码运行没问题,如果您有更好的想法,请贴到下方评论区。
防抖debounce
看了网上很多人的防抖代码,一上来就贴全部代码,实在看不懂,我在理解了防抖概念后,写了一个简单版防抖并做了封装。如果您有更好的想法和思路,请贴到下方评论区教教我。
思路
我目前对防抖的概念就是使用清除定时器来对用户的操作进行限制,相当于一个cd条,如果打断了,就不执行(清除定时器)。
比如我现在要做一个button按钮,点击两秒钟后打出一句123,说干就干。
let btn=document.querySelector('button')
btn.addEventListener('click',()=>{
setTimeout(()=>{console.log(123)},1000)
})
没有防抖前效果如下 简单做个防抖
let btn=document.querySelector('button')
let timer=null
btn.addEventListener('click',()=>{
clearTimeout(timer) //每点一次都清除上一次的timer,上一次的就不会执行
timer=setTimeout(()=>{console.log(123)},1000)
console.log(timer)
//这句代码是为了证明获取timer比console早
//只要执行了setTimeout就拿到timer了,不用执行参数函数console
})
效果如下:
优化代码
let btn=document.querySelector('button')
function debounce(handler,delay){//delay就是1000ms
let timer=null
return function (){
clearTimeout(timer)
timer=setTimeout(handler,delay)
}
}
const handler=()=>{console.log(123)}
btn.addEventListener('click',debounce(handler,1000))
优化代码思路:
逻辑是基于上面的简化版进行的函数封装,就是使用闭包,把timer包起来。
然后按照里面的内容拆开写debounce
的参数,以上的参数是处理函数handler(就是console.log(123))和delay(时间),如果有更多的业务逻辑,就写更多的参数做封装就行了。
节流throttle
一开始搞错了节流的概念,看着网友们的描述,还以为节流是个循环函数。
后来发现节流实际上就是不管用户点多少次,我只执行第一次。
节流与防抖的区别
它跟防抖的关系好比一个法师在施法,如果这个法师不断在施法,但是它的技能是可以被打断的,每打断一次都重新触发施法那就是防抖。
如果这个法师的技能不能被打断,不管你打了多少次,法师都能把刚开始的施法读条delay给做完,这就是节流。
示例
没有节流的效果
思路:如何让上面的代码在我设置的时间范围内只跑一次呢?还是使用setTimeout实现,如果有一个开关,当开关是打开的时候,就跑代码。如果开关闭合,就不执行代码,跑代码过程中将闭合开关不就行了吗?
节流的效果 代码如下
let btn=document.querySelector('button')
const handler=()=>{console.log('初步节流')}
let toggle=true //设置一个开关
btn.addEventListener('click',()=>{
if(!toggle){//如果开关是关着的,就return
return null
}
toggle=false //此时函数正在执行,把开关闭合
setTimeout(()=>{
handler() //这里是处理业务逻辑了
toggle=true //处理完之后把开关打开
},1000)
})
闭包封装
let btn=document.querySelector('button')
const handler=()=>{console.log('闭包封装')}
function throttle(){
let toggle=true //把toggle当做闭包
return function (){
if(!toggle){
return null
}
toggle=false
setTimeout(()=>{
toggle=true
handler()
},1000)
}
}
btn.addEventListener('click',throttle())
优化代码
let btn=document.querySelector('button')
const handler=()=>{console.log('封装函数')}
/*
参数:
handler: 执行函数
delay:时间
*/
function throttle(handler,delay){
let toggle=true
let timmer //定时器
return function (){
if(!toggle){
return null
}
toggle=false
timer= setTimeout(()=>{
clearTimeout(timmer) //清除前一次定时器
handler()
toggle=true
},delay)
}
}
btn.addEventListener('click',throttle(handler,2000))
后话
这篇博客主要是用来记录一下今天看文档的心得,虽然不多,但是也结合了自己的思考,做个记录,如果您对上面的代码有意见,请在下方评论区帮我优化一下,不慎感激。