# JavaScript 运行机制-EventLoop

# 单线程

JavaScript 是单线程语言,为什么不支持多线程呢?这是 JavaScript 的运行环境所导致的。假如有两个线程,他们同时操作同一个DOM节点,那么这个节点应该采用哪个线程的结果?

虽然HTML5提出了 web Worker 标准, 但此标准创建的线程并不能修改 DOM 所以依然没有改变 JavaScript 单线程的本质。

# 任务队列

既然是单线程,那么所有的任务都要排队执行。一旦某些任务耗时很长,后面的任务就需要一直等待。

耗时的任务,我们一般都要把它们改为异步任务。例如:

// 这部分代码会放在下次循环时的第一个执行
setTimeout(() => {
  // 耗时任务
}, 0)

此时 JavaScript 的设计者就考虑,能否把异步任务任务存放在一个临时的任务队列中。等主进程执行完毕后,再把这些和异步任务相关的任务加载到主线程的末尾继续执行。

于是乎这个临时的队列,我们可以把它称之为 消息队列

当主进程的代码都执行完毕后,去 消息队列 中查看之前丢进去的耗时任务执行完了没有。如果有执行完毕的那就拿出来放在主进程的末尾继续执行。

由此我们可以看出,JavaScript 运行时至少是有两个进程

  • 主进程
  • 消息队列 又叫 消息进程

所以 JavaScript 代码执行的过程是这样的

1. 所有的同步代码,都在 `主进程` 上执行
2. `主进程` 执行的过程中遇到异步任务时
3. 当主进程执行完毕时,立刻去 `消息进程` 中把未执行的一步任务放在主进程的末尾继续执行。
4. 主线程会不断的重复上面的第三步,所以才把这个过程叫做 EventLoop

# setTimeout 带来的问题

我们先来看这一段代码

let start = Date.now()
setTimeout(() => {
  console.log('异步任务')
}, 200)
console.log(start)
for (let i = 0; i < 1e4; i++) {
  console.log(i)
}
console.log(Date.now() - start)

我们希望在程序开始执行 200 毫秒后,在控制台打印 异步任务。但执行的结果并非如你所愿,因为主进程中有一个非常耗时的同步任务。

还记得我们前面说过 EventLoop 是要等待主进程执行完毕,才会去消息进程中拿之前订阅过的异步任务。 虽然我们订阅的是 200 毫秒,但很可惜主进程的执行锁耗费的时间 >200 毫秒。所以 setTimeout 并不能准时的帮我们完成这样的操作。

setInterval 也有同样的问题。

上次更新: 9/26/2020, 2:36:53 AM