Twlig / issuesBlog

MIT License
3 stars 0 forks source link

事件循环 #51

Open Twlig opened 2 years ago

Twlig commented 2 years ago

单线程和非阻塞

javascript是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。

JS是单线程与它的用途有关,作为浏览器脚本,JS主要用途是与用户互动,以及操作DOM,这决定了JS只能是单线程的。假定JS同时有两个线程,一个改变某个DOM节点的内容,另一个删除这个DOM节点,则会发生冲突和错误。

非阻塞则是,为了防止一直处于等待状态,影响用户体验。通过将异步任务放入队列中,可以先执行同步任务。

而“非阻塞”,javascript引擎是如何实现的这一点呢?答案就是——event loop(事件循环)

执行栈和事件队列

当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈。执行栈中存放方法的执行上下文,执行方法时从栈顶执行,执行结束弹出该方法的执行上下文,并继续执行下一个栈顶方法。

同步代码可以按照顺序直接依次加入执行栈,而对于异步代码则不能直接加入执行栈,需要用到事件队列

js引擎遇到一个异步事件后会将事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将事件加入事件队列

加入事件队列的回调不会被立刻执行:

  1. 等待主线程处于闲置状态

    事件被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。

  2. 执行事件队列中的任务对应的回调

宏任务和微任务

异步任务也有不同,执行优先级也有区别。不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)。

以下事件属于宏任务:

以下事件属于微任务

事件循环过程

首先,整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为同步任务、异步任务两部分

同步任务会直接进入主线程依次执行

异步任务会再分为宏任务和微任务

宏任务进入到Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到事件队列中

微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到事件队列中

当主线程内的任务执行完毕,主线程为空时,会检查微任务的事件队列,如果有任务,就全部执行,如果没有就执行下一个宏任务(不会执行全部宏任务)

上述过程会不断重复,这就是Event Loop,比较完整的事件循环