使用 setTimeout 模拟 setInterval
使用 setTimeout 模拟 setInterval,实现如下功能:
const intervalID = setInterval(cb, interval)
clearInterval(intervalID)
首先想到方案:
function _setInterval(cb, interval) {
let intervalID;
const recur = () => {
cb();
// 闭包更新intervalID
intervalID = setTimeout(() => {
// 递归调用自己
recur();
}, interval);
};
intervalID = setTimeout(recur, interval);
return intervalID;
}
function _clearInterval(intervalID) {
clearTimeout(intervalID);
}
本方案存在问题:执行 _setInterval 的时候返回的 intervalID 依然不是最新的 intervalID
改进方案:
let _timer = {};
function _setInterval(cb, interval) {
let timeoutID = parseInt(Math.random() * 10) + 1;
const recur = () => {
cb();
// 闭包更新intervalID
_timer[timeoutID] = setTimeout(() => {
// 递归调用自己
recur();
}, interval);
};
_timer[timeoutID] = setTimeout(recur, interval);
return timeoutID;
}
function _clearInterval(intervalID) {
clearTimeout(_timer[intervalID]);
delete _timer[intervalID];
}
注:setInterval返回值intervalID是一个非零数值,用来标识通过setInterval()创建的计时器,这个值可以用来作为clearInterval()的参数来清除对应的计时器
若全局变量intervalID定义为Number类型,虽然每次递归会更新intervalID的值,但是_clearInterval只能取到intervalID的值拷贝而不是引用,导致clearTimeout无法清理最新的定时器,
故将intervalID定义为Object类型let _timer = {},_setInterval返回intervalID作为_timer的key,每次setTimeout更新的是_timer[timeoutID]的引用。这样,调用 _clearInterval 虽然获取到的 intervalID 是不变的,但是可以通过引用关系 _timer[timeoutID] 获取到 setTimeout 返回的最新timeoutID,使用clearTimeout清除即可
注:const intervalID = setInterval(cb, interval); clearInterval(intervalID); 这里需要返回一个 intervalID 的引用,但是intervalID又只能是正整数,只能采取重写 valueOf 的方式实现
不使用全局变量方案如下:
function _setInterval(cb, interval, ...args) {
const _timer = {
value: -1,
valueOf: function () {
return this.value;
},
};
const recur = () => {
_timer.value = setTimeout(recur, interval);
cb.apply(this, args);
};
_timer.value = setTimeout(recur, interval);
return _timer;
}
function _clearInterval(intervalID) {
clearTimeout(intervalID);
}
实现自定义Hook useTimeout
import React, { useEffect, useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const [timeoutCount, setTimeoutCount] = useState(0);
useEffect(() => {
setTimeout(()=> {
setTimeoutCount(count)
}, 3000)
setCount(5);
}, []);
return (
<div>
<div>Count: {count}</div>
<div>timeoutCount: {timeoutCount}</div>
</div>
);
};
第一问:渲染结果
Count: 5
timeoutCount: 0
第二问:实现自定义Hook useTimeout(cb, delay) 实现 3000ms后,timeoutCount更新为count值
// useTimeout
import { useEffect, useRef } from "react";
function useTimeout(cb, timer, deps = []) {
const callbackRef = useRef(cb);
useEffect(() => {
if (timer < 0 || typeof callbackRef.current !== "function") return;
callbackRef.current = cb;
const timeId = setTimeout(() => {
callbackRef.current()
}, timer);
return () => clearTimeout(timeId);
}, deps);
}
export default useTimeout;
import React, { useEffect, useState } from "react";
import useTimeout from "./useTimeout";
const App = () => {
const [count, setCount] = useState(0);
const [timeoutCount, setTimeoutCount] = useState(0);
useTimeout(() => {
setTimeoutCount(count)
}, 3000, [count])
useEffect(() => {
setCount(5);
}, []);
return (
<div>
<div>Count: {count}</div>
<div>timeoutCount: {timeoutCount}</div>
</div>
);
};
参考文章
用setTimeout和clearTimeout简单实现setInterval与clearInterval
第 133 题:用 setTimeout 实现 setInterval,阐述实现的效果与setInterval的差异