# JavaScript教程 - 15 定时器与事件循环
下面介绍一下定时器和 JavaScript 中的方法调用。
# 15.1 定时器
定时器可以延时或周期性的执行函数。
# 1 setTimeout
setTimeout(callback, delay)
函数可以延时执行某个函数一次。
举个栗子:
setTimeout(() => {
alert("2 秒后执行一次");
}, 2000); // 单位是毫秒
2
3
- 第一个参数是回调函数,延时时间到了以后要执行的函数;第二个参数是延迟时间(单位毫秒)。
如果要取消定时器,可以使用 clearTimeout(timer)。
举个栗子:
const timerId = setTimeout(() => {
alert("2 秒后执行一次");
}, 2000); // 单位是毫秒
clearTimeout(timerId); // 取消执行
2
3
4
5
setTimeout()
会返回一个定时器的ID,可以通过 ID 使用clearTimeout()
函数取消定时器。
# 2 setInterval
setInterval(callback, interval)
函数可以每隔一段时间重复执行函数(无限次,除非取消)。
举个栗子:
setInterval(() => {
console.log("每隔 1 秒执行一次");
}, 1000);
2
3
- 上面会每隔一秒执行一次回调函数。
可以使用 clearInterval
清除定时器,下面我们模拟一个倒计时:
let count = 5;
const timer = setInterval(() => {
console.log(count);
count--;
if (count < 0) {
clearInterval(timer); // 停止计时器
console.log("倒计时结束!");
}
}, 1000);
2
3
4
5
6
7
8
9
# 15.2 方法调用
先查看下面的代码:
<script>
function c() {
console.log('c');
debugger;
}
function b() {
console.log('b');
c();
}
function a() {
console.log('a');
b();
}
a();
console.log('代码执行完成!');
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 代码很简单,
a()
函数调用b()
函数,b()
函数调用c()
函数。
当 JavaScript 程序开始执行时,首先会创建一个全局执行上下文,也就是下图的 全局栈帧
,把它压入调用栈的最底部,全局栈帧中包含全局变量、全局函数等。
当调用 a() 函数,会将 a() 函数压入调用栈,当 a() 函数调用 b() 函数,会将 b() 函数也压入调用栈,依次类推,将 c() 函数也压入调用栈。栈帧中包含函数中定义的变量和函数声明等。
当函数调用完成,对应的栈帧会被弹出栈,如下图:
代码在执行的时候,我们可以使用浏览器的开发者工具,通过添加断点 debugger 来查看调用栈,如下图:
所以当全局作用域的代码被执行完毕后,调用栈是空的。也就是上面的代码 console.log('代码执行完成!')
执行完成后,调用栈为空。
# 15.3 事件循环与任务队列
JavaScript 是一种单线程语言,这意味着它一次只能做一件事。然而它可以执行异步操作,就像上面的定时器,但是回调函数并不是立即被压入到调用栈中执行的。
在定时器计时结束后,会将回调函数放入到 任务队列 中排队,等待执行,当调用栈为空时,才会将回调函数放入到调用栈中执行。
所以如果调用栈不为空,定时器时间到了,回调函数也不会被执行的。
举个栗子:
<script>
setTimeout(() => {
console.log("定时器结束");
}, 2000);
let startTime = Date.now();
while (true) {
if (Date.now() - startTime > 5000) {
console.log("过了5秒");
break;
}
}
console.log("主程序执行完成");
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 在上面的代码中,首先定义了一个定时器,2秒后执行;
- 然后主线程中定义了一个死循环,不停的获取当前时间和一开始的时间比较,如果过了5秒,则跳出循环。按理说在这个过程中,定时器倒计时应该结束了,执行回调函数,但是你会发现,回调函数是在主程序执行完成后调用的。
执行结果:
过了5秒
主程序执行完成
定时器结束
2
3
这是因为主程序没有执行完成,那么全局作用域的栈帧就一直在调用栈中,此时调用栈就不为空,回调函数就不会被压入到调用栈中执行,一直在事件队列中排队等待执行。主程序执行完成,调用栈为空,就立马将回调函数压入调用栈执行。