听说有个学长面试被面试官拷打了这个问题,吓得我赶紧写篇博客压压惊。
setTimeout
和 setInterval
是 JavaScript 中用于定时执行代码的两个重要函数。它们允许你指定一段代码(通常是一个函数)在经过一定延迟后执行,或者每隔一定时间间隔重复执行。这两个函数都属于浏览器的 Web API,而不是 JavaScript 语言本身的一部分。
1. 基础知识
1.1 setTimeout
setTimeout
用来设置一个计时器,在指定的时间毫秒数(ms)之后执行一次给定的回调函数。
let timeoutID = setTimeout(callback, delay, [param1, param2, ...]);
callback
:这是将要执行的函数或代码字符串。delay
:这是一个数值,表示多少毫秒后执行回调函数。如果这个值为0或被省略,那么回调函数将会尽可能快地被执行,但不是立即执行,因为它是异步的。[param1, param2, ...]
:这些是可选参数,会作为参数传递给回调函数。
返回值是一个唯一的标识符 (timeoutID
),你可以使用它来取消定时器,通过调用 clearTimeout(timeoutID)
。
1.2 setInterval
setInterval
类似于 setTimeout
,但它不是只执行一次回调函数,而是按照指定的时间间隔(delay
)不断重复执行,直到你显式地停止它。
let intervalID = setInterval(callback, delay, [param1, param2, ...]);
- 参数和返回值与
setTimeout
相同,但是setInterval
会在每次延迟结束后重复调用回调函数,直到你调用clearInterval(intervalID)
来停止它。
1.3 单线程与异步执行
JavaScript 是单线程语言,意味着它只有一个主线程来处理任务。这使得同一时间只能执行一个任务,而其他的任务必须等待当前任务完成。对于 setTimeout
来说,它是异步执行的,这意味着当调用 setTimeout
时,它会将回调函数安排在未来某个时间点执行,但这个回调函数会被放入事件循环(event loop)中。因此,主线程将继续执行后续的同步代码,只有当所有同步任务都执行完毕后,事件循环才会开始检查并执行已到时间的定时器回调。
// 异步任务
const timeout = setTimeout(function(){console.log('456')},5000) // 不会执行
console.log(123)
// 同步任务 阻塞线程
while(true){
console.log('123')
}
2. setTimeout -> setInterval 代码实现
2.1 定义 customSetInterval
函数
function customSetInterval(fn, t) {
let intervalId = null;
function loop() {
intervalId = setTimeout(() => { fn(); loop(); }, t);
}
loop();
return () => clearTimeout(intervalId);
}
-
参数:
fn
: 你想要周期性执行的函数。t
: 每次执行之间的延迟时间(以毫秒为单位)。
-
内部变量和方法:
intervalId
: 用来存储setTimeout
的返回值,即计时器的ID。这个ID可以用来取消定时器。loop()
: 这是一个递归函数,它会首先调用传入的fn
函数,然后再次设置一个setTimeout
来安排下一次的loop()
调用,从而形成循环。
-
启动与返回:
- 当
customSetInterval
被调用时,它立即调用了loop()
方法开始第一次执行。 - 最后,
customSetInterval
返回一个匿名函数,这个函数的作用是清除当前的intervalId
,以此来停止定时器。
- 当
2.2 创建并运行定时器
const interval = customSetInterval(function () { console.log(123); }, 1000);
- 这行代码创建了一个新的定时器,它会每秒打印数字
123
到控制台。interval
是指向清除定时器的函数的引用。
2.3 停止定时器
setTimeout(() => { interval(); }, 5000);
- 这里使用了
setTimeout
来安排在5秒后执行interval
函数,这将清除由customSetInterval
创建的定时器,从而停止每秒打印123
的行为。
2.4 注意事项
- 内存管理: 在这里,
customSetInterval
返回了一个清除定时器的方法,使得我们可以显式地停止定时器,避免不必要的资源占用。 - 非精确性: 尽管我们设定了1000毫秒的间隔,但由于 JavaScript 的单线程性质以及事件循环的工作方式,实际的执行时间可能会有所偏差。
- 递归调用:
loop()
函数通过递归的方式不断调用自己,这种方式可以有效模拟setInterval
的行为,但需要注意的是,如果回调函数执行的时间超过了设定的时间间隔(t
),那么下一次回调的执行将会被推迟。
2.5 源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>setTimeout->setInterval</title>
</head>
<body>
<script>
function customSetInterval(fn,t){
let intervalId = null;
function loop(){
intervalId = setTimeout(()=>{fn();loop();},t)
}
loop();
return ()=> clearTimeout(intervalId)
}
const interval = customSetInterval(function(){console.log(123)},1000)
setTimeout(()=>{interval()},5000)
</script>
</body>
</html>
3.总结
通过以上方法我们可以实现使用setTimeout实现setInterval,主要是通过递归来模拟setInterval的相关功能。怎么说,准备好面对面试官的拷打了吗?