搞清楚JS是怎么跑起来的,很多问题将迎刃而解。
进程:CPU分配资源的最小单位
线程:CPU调度资源的最小单位
一个应用程序可能包含多个进程,当一个应用程序被启动时,一个进程就被创建了,操作系统会为它分配内存。这个进程还可能向操作系统申请另一个进程。两个进程使用进程间通信方式通信。而且,假如其中一个进程挂了,而其他进程还在正常工作时,它又会重启。
一个进程可以拥有多个线程,它们各自完成自己的任务。
为什么要说这个,因为浏览器一般都是多进程的。而为什么要讲浏览器,因为它是一个js的运行时环境。先说浏览器。
以chrome为例,chrome浏览器拥有多个进程,
- 浏览器进程:最核心的进程,负责标签页的创建销毁,网络资源管理,界面显示、用户交互、子进程管理,同时提供存储等功能下载等
- 插件进程:管理插件。每个插件使用时都会创建该插件对应的进程。方便对插件的管理,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
- gpu进程:3D绘制 硬件加速
- 渲染进程:浏览器为每个窗口分配一个渲染进程。这个也叫作浏览器内核。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中
现在着重来看浏览器内核——渲染进程 因为JS引擎就在这里
浏览器内核包含了多个线程
- GUI线程:
1.负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等
2.当界面需要重绘或由于某种操作引发回流时,该线程就会执行
3.GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
这样设计是为了防止:JS是可以操作dom,设置css的,如果两者一起执行,那么渲染的数据就不对了。所以它们是互斥的
- JS引擎线程
1.JS引擎线程是单线程。常见的JS引擎如V8引擎等。
2.JS引擎线程负责解析Javascript脚本,运行代码
3.为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,
但是子线程完全受主线程控制,且不得操作DOM。
- 定时器触发线程
前面的文章说过,像setTimeout这种函数并不是由JS引擎完成的。
因为JS是单线程的, 如果任务队列处于阻塞线程状态就会影响记计时的准确
因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列(宏任务队列)中,等待JS引擎空闲后执行)
W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms
- 事件触发线程
1.它属于渲染进程而不是JS引擎,用来控制事件轮询。
2.当JS引擎执行代码块如AJAX异步请求(这里只是举个例子,事实上,异步请求有专门的线程处理,就在下面)等,会将对应任务添加到事件触发线程中
当请求完毕,这个事件触发线程就会将其扔到任务队列,等待JS引擎有空时再去执行。
- 异步http请求线程
1.用于处理请求XMLHttpRequest,在连接后是通过浏览器新开一个线程请求。如ajax,是浏览器新开一个http线程
2.检测到状态变更(如ajax返回结果)时,如果设置有回调函数,异步线程就产生状态变更事件,
将这个回调再放入js引擎线程的事件队列中。再由JavaScript引擎执行。
终于到了JS引擎了
首先回答一个问题:为什么JS需要一个运行时环境?
其实答案就在上面,像定时器触发线程,事件触发线程等,因为它们的配合,JS才能如鱼得水
- JS的事件循环
JS引擎包含了一个调用栈和一个内存堆。
所有任务都在主线程上执行,形成一个执行栈(调用栈)主线程会不停的从执行栈中读取事件,会执行完所有栈中的同步代码。当遇到异步任务时,如ajax请求,就会交由上面说的http异步请求线程处理,该线程处理完毕后,如果注册了回调函数,那么此时该线程就会将该回调函数放到任务队列。等待JS引擎有空再去执行。然而这个任务队列还分为了微任务队列和宏任务队列。
宏任务队列:setTimeout setInterval script(整体代码)等
微任务队列:promise 、MutaionObserver、process.nextTick(Node.js环境)
JS引擎在一次事件循环中,也就是执行栈的同步代码执行完后会先去微任务队列查看,如果有就执行,没有再去宏任务队列查看