总结:JavaScript异步、事件循环与消息队列、微任务与宏任务

本人正在努力学习前端,内容仅供参考。由于各种原因(不喜欢博客园的UI),大家可以移步我的github阅读体验更佳:,喜欢就点个stat咯,或者我的博客:https://blog.tangzhengwei.me
掘金:传送门,segmentfault:传送门

前言

Philip Roberts 在演讲 great talk at JSConf on the event loop 中说:要是用一句话来形容 JavaScript,我可能会这样:

JavaScript 是单线程、异步、非阻塞、解释型脚本语言。”

单线程 ?

异步 ? ?

非阻塞 ? ? ?

然后,这又牵扯到了事件循环、消息队列,还有微任务、宏任务这些。

作为一个初学者,对这些了解甚少。

这几天翻阅了不少资料,似乎了解到了一二,是时候总结一下了,它们困扰了我好一段时间,就像学高数那会儿自己去理解一个概念一样。

单线程与多线程

单线程语言:JavaScript 的设计就是为了处理浏览器网页的交互(DOM操作的处理、UI动画等),决定了它是一门单线程语言。

如果有多个线程,它们同时在操作 DOM,那网页将会一团糟。

JavaScript 是单线程的,那么处理任务是一件接着一件处理,从上往下顺序执行:

console.log('script start') console.log('do something...') console.log('script end') // script start // do something... // script end

上面的代码会依次打印: "script start" >> "do something..." >> "script end"

那如果一个任务的处理耗时(或者是等待)很久的话,如:网络请求、定时器、等待鼠标点击等,后面的任务也就会被阻塞,也就是说会阻塞所有的用户交互(按钮、滚动条等),会带来极不友好的体验。

但是:

console.log('script start') console.log('do something...') setTimeout(() => { console.log('timer over') }, 1000) // 点击页面 console.log('click page') console.log('script end') // script start // do something... // click page // script end // timer over

"timer over" 在 "script end" 后再打印,也就是说计时器并没有阻塞后面的代码。那,发生了什么?

其实,JavaScript 单线程指的是浏览器中负责解释和执行 JavaScript 代码的只有一个线程,即为JS引擎线程,但是浏览器的渲染进程是提供多个线程的,如下:

JS引擎线程

事件触发线程

定时触发器线程

异步http请求线程

GUI渲染线程

浏览器渲染进程参考

当遇到计时器、DOM事件监听或者是网络请求的任务时,JS引擎会将它们直接交给 webapi,也就是浏览器提供的相应线程(如定时器线程为setTimeout计时、异步http请求线程处理网络请求)去处理,而JS引擎线程继续后面的其他任务,这样便实现了 异步非阻塞

定时器触发线程也只是为 setTimeout(..., 1000) 定时而已,时间一到,还会把它对应的回调函数(callback)交给 消息队列 去维护,JS引擎线程会在适当的时候去消息队列取出消息并执行。

JS引擎线程什么时候去处理呢?消息队列又是什么?

这里,JavaScript 通过 事件循环 event loop 的机制来解决这个问题。

这个放在后面再讨论吧!

同步与异步

上面说到了异步,JavaScript 中有同步代码与异步代码。

下面便是同步:

console.log('hello 0') console.log('hello 1') console.log('hello 2') // hello 0 // hello 1 // hello 2

它们会依次执行,执行完了后便会返回结果(打印结果)。

setTimeout(() => { console.log('hello 0') }, 1000) console.log('hello 1') // hello 1 // hello 0

上面的 setTimeout 函数便不会立刻返回结果,而是发起了一个异步,setTimeout 便是异步的发起函数或者是注册函数,() => {...} 便是异步的回调函数。

这里,JS引擎线程只会关心异步的发起函数是谁、回调函数是什么?并将异步交给 webapi 去处理,然后继续执行其他任务。

异步一般是以下:

网络请求

计时器

DOM时间监听

...

事件循环与消息队列

回到事件循环 event loop

其实 事件循环 机制和 消息队列 的维护是由事件触发线程控制的。

事件触发线程 同样是浏览器渲染引擎提供的,它会维护一个 消息队列

JS引擎线程遇到异步(DOM事件监听、网络请求、setTimeout计时器等...),会交给相应的线程单独去维护异步任务,等待某个时机(计时器结束、网络请求成功、用户点击DOM),然后由 事件触发线程 将异步对应的 回调函数 加入到消息队列中,消息队列中的回调函数等待被执行。

同时,JS引擎线程会维护一个 执行栈,同步代码会依次加入执行栈然后执行,结束会退出执行栈。

Stack&Queue

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyydwg.html