都说setInterval不好,那么就用setTimeout来模拟一下吧
单纯记录一下常见面试题
setInterval和setTimeout
setTimeout和setInteval是window对象上两个主要的定时方法, 区别在于setTimeout是定时程序,即在到达指定时间之后执行,回调函数只会执行一次,而setInterval反复执行回调函数,但间隔一段时间。
所以通过setTimeout实现setInterval的本质在于递归调用
const mySetInterval = (callback, duration) => {
function fn () {
callback() // 执行回调
setTimeout(()=> {
fn() // 在setTimeout内递归调用
}, duration)
}
setTimeout(fn, duration) // 这里不能写fn(),大概只有菜鸡才会明白
}
我们实现了setInterval的功能,但是,这个setInterval没有办法停止运行,会一直执行下去,甚至比之前的setInterval更差了。
所以,我们需要实现clearInterval
clearInterval 和 clearTimeout
setTimeout和setInterval在设置好之后,会返回一个定时器的排队序号,我们可以通过该序号,取消已存在的定时器。
let timer1 = setTimeout(() => {
// 定时器即使清除了,其返回值也不会清除,之后设置定时器的返回值也会在其返回值的基础上继续向后排,
// 类似于银行的排队领号,即使1号的业务办理完了,后面的人仍是从2号开始继续领号,而不是从1开始。
clearTimeout(timer1)
}, 1000)
let timer2 = setInterval(() => {
// 定时器需要手动清除,并且clearTimeout和clearInterval都可以清除setTimeout或setInterval,但并不建议这样做,容易造成混淆。
clearInterval(timer2)
}, 500)
由于setTimeout和setInterval虽然具有不同功能,但其实质都是浏览器的定时器,所以返回的序号是依次排列的。另外,setInterval设置后会有一个固定的返回值,这个代表序号的返回值不变(设置定时器就有返回值,执行多少次是定时器的处理)。
所以,对于setTimeout模拟的setInterval,我们只要拿到了fn内部的定时器序号就可以实现clearInterval的功能。
我们可以通过闭包,维护一个不断更新的timeId
// 创建myTimer对象
const myTimer = (
function () {
let timeId // 创建timeId
function mySetInterval (callback, duration) {
const preTimeId = timeId
function fn () {
callback() // 执行回调
// 更新timerId
timerId = setTimeout(() => {
fn() // 在setTimeout内递归调用
}, duration)
// 取消上一次的定时器
clearTimeout(preTimeId)
}
// 第一次设置定时器,并初始化timerId
timerId = setTimeout(fn, duration)
// 将timerId返回
return timerId
}
function myClearInterval (id) {
// 调用时传入实时更新的timerId
clearTimeout(id)
}
retrun {
setInterval: mySetInterval,
clearInterval: myClearInterval
}
})()
现在,我们通过myTimer可以调用mySetInterval和myClearInterval
let id = myTimer.setInterval(()=>{
console.log('hi')
}, 500)
setTimeout(() =>{myTimer.clearInterval(id)}, 5000)
但是,定时器并没有如我们相信的停止
因为我们的id,只获得了第一次setTimeout的id,因为我们的id是Number类型,它是timerId的值拷贝而非引用,而setTimeout是Number类的包对象。所以id不能如我们所想的那样,如实时更新timerId的值。
解决办法也很简单,让我们的id引用timerId
let timerId = { id: null}
function mySetInterval (callback, duration) {
const preTimeId = timeId
function fn () {
callback() // 执行回调
// 更新timerId
timerId.id = setTimeout(() => {
fn() // 在setTimeout内递归调用
}, duration)
// 取消上一次的定时器
clearTimeout(preTimeId)
}
// 第一次设置定时器,并初始化timerId
timerId.id = setTimeout(fn, duration)
// 将timerId返回
return timerId
}
function myClearInterval (id) {
// 调用时传入实时更新的timerId
clearTimeout(id.id)
}
这样,我们就获得了setTimeout和clearTimeout模拟的setInterval和clearInterval