JavaScript运行机制总结
文章摘抄总结至 此处,只用于个人学习啊啊啊啊啊啊,侵权删
从浏览器说起
1.浏览器是多进程的
2.浏览器的每一个tab页面相当与一个独立的浏览器进程,某些情况tab页面可能被浏览器合并
3.浏览器包括的进程与多进程优点如下图所示:
图1-1 浏览器的多线程及其优点渲染进程(Renderer进程)
1. 页面的渲染,JS的执行,事件的循环,都要集中在 渲染进程
2. 渲染进程是多线程的
3. 渲染进程的常驻线程及其作用如下图所示:
图1-2 渲染进程的常驻线程及其作用浏览器主进程(Browser进程)与渲染进程的通信
如下图所示:
图1-3 浏览器主进程(Browser进程)与渲染进程通信流程涉及到的进程:
图1-4 浏览器渲染时进程之间的通信渲染进程中的线程
1. GUI渲染线程与JS引擎线程互斥
在图1-2 渲染进程的常驻线程及其作用 中提到 GUI渲染线程与JS引擎线程互斥,因为JavaScript是可以操作DOM的元素的(DOM提供了接口),同时操作DOM和渲染页面是不大现实的,因为数据不一致。故在JS引擎执行时,GUI渲染线程是被挂起的,待JS引擎空闲时恢复。故这一机制会有如下问题。
2. JS会阻塞页面的加载
由于 GUI渲染线程与JS引擎线程互斥 ,JS引擎在巨量运算时,GUI渲染线程只能干等,在用户上看就有卡顿的感觉,页面渲染不连贯。
3. Web Worker
创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM);JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)。
综上,Web Worker是另开一个线程,用于解决计算量大的问题,而JS引擎依旧是单线程。
另外 SharedWorker 与 WebWorker 本质上就是进程和线程的区别,haredWorker由独立的进程管理,WebWorker只是属于render进程下的一个线程
浏览器渲染流程
浏览器在渲染过程中,主要涉及到 渲染进程的GUI渲染线程。参考下面图片:
图1-5 浏览器渲染页面流程图JS运行机制与Event Loop
1. JS分为同步任务和异步任务
2. 同步任务都在 主线程(JS引擎线程) 上执行,形成一个 执行栈
3. 主线程之外,事件触发线程 管理着一个任务队列,只要异步任务(如定时器触发线程计时完毕等)有了运行结果,就在任务队列之中放置一个事件。(这里的回调函数还涉及到 定时触发器线程 和 异步http请求线程 的回调函数)
4. 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
如下图:
图1-6 JS事件循环图 图1-7 JS事件循环图2故JS的事件循环(Event Loop)过程是:
1. 主线程(JS引擎线程)运行时会产生执行栈, 栈中的代码调用某些api(上图所示)时,当满足触发条件后,(如ajax请求完毕有回调函数)它们会在事件队列中添加各种事件
2. 当 栈中的代码执行完毕,就会读取(队列头部开始)事件队列中的事件,放入执行栈中,去执行那些回调
3. 重复1,2
综上,JS在运行的时候主要涉及到 渲染进程的两个线程——JS引擎线程 和 事件触发线程,另外,在执行代码时,可能会触发到 定时器触发线程 以及 异步http请求线程。
事件循环的另一个角度:macrotask与microtask
原因:在ES6之后,Promise里有了一个新的概念:microtask
如此一来,JS中分为两种任务类型:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task。
macrotask:
1. macrotask(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
2. 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
microtask:
1. microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务
2. 也就是说,在当前task任务后,下一个task之前,在渲染之前执行
3. 在某一个macrotask(宏任务)执行完后,就会将在它执行期间产生的所有microtask(微任务)都执行完毕(在渲染前)
4. 微任务有微任务队列(Job Queues)
宏任务与微任务分类:
macrotask:主代码块,setTimeout,setInterval,setImmediate,MessageChannel (优先级先于setTimeout)等(可以看到,事件队列中的每一个事件都是一个macrotask)
microtask:Promise,process.nextTick,MutationObserver(优先级最小)等
另外,setImmediate是有规定:在下一次Event Loop(宏任务)时触发(所以它是属于优先级较高的宏任务), (Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面
综上,一次的Event Loop(事件循环) 的运行机制就是以下几个步骤:
1. 执行一个宏任务(第一次肯定是栈中的代码块,没有就从事件队列中获取)
2. 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
3. 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(当没有优先级时依次执行)
4. 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
5. 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
如下图所示:
图1-8 事件循环之宏任务与微任务图注:有一些浏览器执行结果不一样(因为它们可能把microtask当成macrotask来执行了), 但是为了简单,这里不描述一些不标准的浏览器下的场景(但记住,有些浏览器可能并不标准)。另外,Promise的polyfill与官方版本的实现不同。polyfill,一般都是通过setTimeout模拟的,所以是macrotask形式。
参考文章:
1. http://www.dailichun.com/2018/01/21/js_singlethread_eventloop.html
2. https://segmentfault.com/a/1190000017204460
网友评论