假如我是路边卖水果摊的小老板,遇到这个问题了,想尽快寻找答案。 靠!小老板啥都不知道,只看懂了前面 “说说” 两个字,还能怎么办?百度吧
1. 先查一下什么是 JS。
百度结果,小老板懂了,哦!JS 是一种脚本语言。ECMA国际组织为它制定了统一的 ECMAScrip 标准。现在主要被用于浏览器的web开发,以及服务器编程(Node.js)
- 浏览器的JS:
JS = ECMAScript + DOM + BOM
- 服务器Node.js:
JS = ECMAScript + os + file + net + database
2. 小老板重新定义了问题:“说说浏览器中 JS 的运行机制?”
小老板想了想:“靠!浏览器的本身怎么运行我都不知道,更别说浏览器里的 JS ”,于是只好继续百度。
一番查找后,小老板了解到,电脑里的应用程序,一般由多个进程组成,而每个进程又由多个线程组成。而浏览器中 JS 脚本的执行,就是一个线程负责,就是JS 引擎线程
。
3. 小老板又重新定义了问题:“说说浏览器中JS 引擎线程
的运行机制?”
小老板悟性很高,立马想到,要深入去解析
JS 引擎线程
的运行机制,不如先了解一下,包含这个线程的进程是如何运行的。
这时小老板了解到浏览器的进程
都有哪些了,其中JS 引擎线程
就在渲染进程
里面:
- Browser进程
- 浏览器的主进程(负责协调、主控),该进程只有一个
- 负责浏览器界面显示,与用户交互。如前进,后退等
- 负责各个页面的管理,创建和销毁其他进程
- 将渲染(Renderer)进程得到的内存中的Bitmap(位图),绘制到用户界面上
- 网络资源的管理,下载等
- 第三方插件进程
- 每种类型的插件对应一个进程,当使用该插件时才创建
- GPU进程
- 该进程也只有一个,用于3D绘制等等
渲染进程
- 即通常所说的浏览器内核(Renderer进程,内部是多线程)
- 每个Tab页面都有一个渲染进程,互不影响
- 主要作用为页面渲染,脚本执行,事件处理等
4. 小老板又又重新定义了问题:“说说浏览器中渲染进程
里JS 引擎线程
的运行机制?”
啥都不懂的小老板,只好继续百度了。先查一下
渲染进程
什么时候会工作。
简单了解了下浏览器中输入 url 到出现页面,发生的一系列反应:
- DNS 查询
- TCP 连接
- HTTP 请求即响应
- 服务器响应
客户端渲染
为了解答问题,小老板现在丝毫不关心前4点浏览器究竟干了什么。很显然第5点才是渲染进程
用武之地。比如在第4点的时候,服务器响应了下面的html文件。
<!-- 小老板语气比较暴躁,请谅解~ -->
<!DOCTYPE html>
<html lang="en">
<script>alert('Fire in the hold!')</script>
<body>
<div>Son of Bittch!</div>
</body>
</html>
然后小老板又先进一步了解渲染进程
具体的工作步骤:
处理 HTML 标记并构建 DOM 树
。- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
以及渲染进程
中的两个比较重要的线程:
- GUI渲染线程
- 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等
- JS引擎线程
- JS引擎线程就是JS内核,负责处理Javascript脚本程序
- GUI渲染线程与JS引擎线程是互斥的,JS 引擎线程会阻塞GUI渲染线程
小老板结合渲染进程
的工作步骤和两个关联的线程分析得知:在解析上面的 html 文件的时候,JS 引擎线程
会在第1点步骤的时候发挥作用,也就是GUI渲染进程
解析到<script>alert('Fire in the hold!')</script>
时,直接交给了JS 引擎线程
处理了。(终于来到主角了。JS 执行阻塞DOM构建,小老板先放一边不管了)。
4. 小老板再次总结,又又又重新定义了问题:“说说浏览器中渲染进程
里JS 引擎线程
的运行<script>alert('Fire in the hold!')</script>
的机制?”
依旧是啥都不懂的小老板,发起了灵活拷问:计算机是如何读懂 Javascript 的?毕竟对猩猩而言,代码只不过是一串符号。
小老板想了一下,这有啥好说的,既然是单线程,代码只能一行接一行的执行。就直接弹窗 Fire in the hold! 呗。
这时,小老板意识到问题应该没那么简单。连夜学了几句 JS:
setTimeout(() => {
console.log(0);
}, 1000);
new Promise((resolve) => {
console.log(1);
resolve();
}).then(() => {
console.log(2);
});
console.log(3);
好家伙,打印顺序是:1320
。根本不讲武德,说好的按顺序执行呢?
小老板这里理性分析了一波:受限于单线程JS 引擎线程
只能同步执行脚本,这一点他确凿无疑。之所以不按顺序打印,肯定是执行的顺序变了。是什么导致执行的顺序改变呢?小老板经过百度得知,原来是JS 引擎线程
的执行机制:Event Loop
。
5. 小老板再次归纳问题:“说说浏览器中渲染进程
里JS 引擎线程
中Event Loop
的执行机制”
啥都不懂的小老板,经过多番研究(百度)。明白了Event Loop
的机制的一些缘由。
1. 为啥要用Event Loop
执行机制。
比如有个需求,需要点击页面后,可以领取红包。比如实现的脚本代码如下:
click(document.body) // 没有 Event Loop 的 JS,会卡住在这里,一直等待用户的点击
alert("Fuck you man!")
click(document.body) // 没有 Event Loop 的 JS,用户只能任由程序的摆布。
alert("给,你的红包!")
小老板对只能 “同步执行的JS” 卧槽了一句:“你在教我做事?” 小老板只能按照脚本的顺序操作,不然页面就卡住,毫无用户体验。为此Event Loop
执行机制,应运而生!!
2. Event Loop
机制原理。
为了解决第1点的问题,在Event Loop
机制下,把任务分成两种:同步任务
和 异步任务
。
机智的小老板一下懂了,同步任务就是只能按顺序一个个地执行脚本。异步任务的脚本则是被通知可以执行的时候再执行。
为了实现异步任务的功能,Event Loop
机制下有一个事件队列
,当异步任务的脚本可以被执行的时候,事件队列
尾部添加这个回调脚本。当JS 引擎执行栈中的代码执行完毕,就会去读取事件队列
,将异步任务所对应的回调脚本添加到执行栈中执行。这个过程不断循环,所以称作:Event Loop
。
经过一番百度,小老板知道原来在渲染进程
中,还有其他线程与JS 引擎线程
协作。
渲染进程
中其他的线程:
-
事件触发线程
- 控制事件循环,并且管理着一个事件队列(task queue)
-
定时触发器线程
- setInterval与setTimeout所在线程,用于计时并触发定时
- W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms
-
异步http请求线程
- 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求
- 为什么是单线程?
- JS 之所以要单线程运行,受限于它在浏览器里用途。试想一下,如果有两个js线程同时操作 Dom,一个要删除节点,一个要修改这个节点,应以哪个为准?
-
单线程如何处理异步任务?
- 讲道理单线程的 JS 只能一个接一个的按顺序执行任务。但是在浏览器中,用户交互,数据请求的操作,要求 JS 要在特定的时刻执行特定的代码。因此需要其他线程的共同协作。
JS 线程把这些任务交给 “事件触发线程”,事件触发线程管理着一个任务队列,一旦其中一个任务有了结果,就会添加一个事件回调。等 JS 线程的同步任务执行完毕,系统就会去查询任务列表的事件回调,把可以执行的任务添加到执行栈当中运行。