JS引擎运行机制

JS引擎运行机制

-- 原地址(自己学习记录): https://juejin.im/post/5a6547d0f265da3e283a1df7#heading-6

解决的面试问题

  1. 浏览器包含的进程

  2. 浏览器内核: 渲染引擎 + JS引擎(V8引擎)

  3. render进程(浏览器内核)包含的进程

  4. 为何JS阻塞线程会导致页面渲染阻塞

  5. 从输入url开始到渲染, 浏览器做了什么

  6. CSS阻塞DOM树的渲染

  7. 任务队列, 宏任务, 微任务的执行顺序等等

大纲

  • 区分进程和线程

    • 浏览器是多进程的

    • 浏览器都包含哪些进程?

    • 浏览器多进程的优势

    • 重点是浏览器内核(渲染进程)

    • Browser进程和浏览器内核(Renderer进程)的通信过程

  • 梳理浏览器内核中线程之间的关系

    • GUI渲染线程与JS引擎线程互斥

    • JS阻塞页面加载

    • WebWorker,JS的多线程?

    • WebWorker与SharedWorker

  • 简单梳理下浏览器渲染流程

    • load事件与DOMContentLoaded事件的先后

    • css加载是否会阻塞dom树渲染?

    • 普通图层和复合图层

  • 从Event Loop谈JS的运行机制

    • 事件循环机制进一步补充

    • 单独说说定时器

    • setTimeout而不是setInterval

  • 事件循环进阶:macrotask与microtask

区分进程和线程


  - 进程是一个工厂,工厂有它的独立资源 -> 系统分配的内存(独立的一块内存)

  - 工厂之间相互独立 -> 进程之间相互独立

  - 线程是工厂中的工人,多个工人协作完成任务 -> 多个线程在进程中协作完成任务

  - 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成

  - 工人之间共享空间 - -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

windows系统

-- 打开任务管理器,可以看到有一个后台进程列表, 浏览器打开多个进程, 以及每个进程内存资源信息以及cpu占有率。

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)

  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

tips

  • 不同进程之间也可以通信,不过代价较大

  • 现在,一般通用的叫法:单线程与多线程,都是指在一个进程内的单和多。(所以核心还是得属于一个进程才行)

浏览器是多进程的

  1. 浏览器的简化理解

    • 浏览器是多进程的
    • 浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
    • 简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。
  2. 任务管理器

    Chrome浏览器打开多个标签页, 在***任务管理器***中看到有多个进程(一个Tab页面一个独立进程外加一个主程)

    浏览器有自己的优化机制, 打开多个空白页面时候, 其实共用一个进程

7.png

浏览器包含的进程

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个, 作用有

    • 负责浏览器界面显示,与用户交互。如前进,后退等

    • 负责各个页面的管理,创建和销毁其他进程

    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上

    • 网络资源的管理,下载等

  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建

  3. GPU进程:最多一个,用于3D绘制等

  4. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为

    • 页面渲染,脚本执行,事件处理等

浏览器多进程的优势

-- 相对于单进程, 多进程的优点

  • 避免单个页面崩溃影响整个浏览器

  • 避免第三方插件崩溃影响整个浏览器

  • 多进程充分利用多核优势

  • 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

重点是浏览器内核(渲染进程)

渲染进程(多线程)包含的线程(也叫浏览器内核: 渲染内核 + JS内核 + 等等)

  1. GUI渲染线程(渲染内核)

    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。

    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

    • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

  2. JS引擎线程(JS内核)

    • 负责处理Javascript脚本程序。(例如V8引擎)

    • JS引擎线程负责解析Javascript脚本,运行代码。

    • JS引擎一直等待着任务队列(事件队列)中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序

    • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

  3. 事件触发线程(web api)

    • 用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)

    • 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中

    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理

    • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

  4. 定时触发器线程

    • 传说中的setInterval与setTimeout所在线程

    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)

    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)

    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。所以, 即便setTimeout设置为0, 事实上也是4ms

  5. 异步http请求线程

    • 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求

    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行

8.png

Browser进程和浏览器内核(Render进程)的通信过程

-- 首先, 打开浏览器, 打开任务管理器, 任务管理器上有两个进程(主控进程, 一个是Tab的渲染进程)

  • Browser进程收到用户请求,首先需要获取页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost接口传递给Render进程

  • Render进程的RenderHost接口收到消息,简单解释后,交给渲染线程,然后开始渲染

  • 渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染

  • 当然可能会有JS线程操作DOM(这样可能会造成回流并重绘)

  • 最后Render进程将结果传递给Browser进程

  • Browser进程接收到结果并将结果绘制出来

9.png

梳理浏览器内核中线程之间的关系

GUI渲染线程(渲染内核)与JS引擎线程(JS内核)互斥

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了

因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起, GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

JS阻塞页面加载

从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。

譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。 然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然页面渲染加载阻塞

WebWorker

前文中有提到JS引擎是单线程的,而且JS执行时间过长会阻塞页面,那么JS就真的对cpu密集型计算无能为力么?

所以,后来HTML5中支持了Web Worker。

MDN的官方解释是:


  Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面

  一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件

  这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window

  因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误

这样理解下:

  • 创建webWorker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)

  • JS引擎线程与webworker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据), webWorker可以理解是浏览器给JS引擎开的外挂, 专门用来解决大量计算问题

WebWorker与SharedWorker

  1. WebWorker: 只属于某个页面, 跟其他Render进程不共享, 是浏览器内核(Render进程)创建新的线程来处理JavaScript程序

  2. SharedWorker: 浏览器开启新的进程, 所有页面共享

  3. SharedWorker由独立的进程管理,WebWorker只是属于render进

浏览器渲染流程

  - 浏览器输入url,浏览器主进程接管,开一个下载线程,
  然后进行 http请求(略去DNS查询,IP寻址等等操作),然后等待响应,获取内容,
  随后将内容通过RendererHost接口转交给Renderer进程

  - 浏览器渲染流程开始

浏览器内核拿到内容后, 渲染分为几个步骤:

  1. 解析html建立dom树

  2. 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树)

  3. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算

  4. 绘制render树(paint),绘制页面像素信息

  5. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。

渲染完成之后就是load事件(windows.onload)

load事件与DOMContentLoaded事件

  1. DOMContentLoaded事件: 仅当DOM加载完成,不包括样式表

  2. load事件: 页面上所有的DOM,样式表,脚本,图片都已经加载完成了。 (渲染完毕了)

css加载是否会阻塞dom树渲染

这里说的是头部引入css的情况

由于css是由单独的下载线程异步下载的。 然后

  • css加载不会阻塞DOM树解析(异步加载时DOM照常构建)

  • 但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)
    这可能也是浏览器的一种优化机制。

因为你加载css的时候,可能会修改下面DOM节点的样式,
如果css加载不阻塞render树渲染的话,那么当css加载完之后,
render树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。
所以干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,
在根据最终的样式来渲染render树,这种做法性能方面确实会比较好一点。

普通图层和复合图层

  1. 普通文档流内可以理解为一个复合图层, absolute(fixed)也都默认是跟普通文档流在同一复合图层中

  2. 如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意

  3. GPU中,各个复合图层是单独绘制的,所以互不影响

  4. 某些动画, 为了防止DOM更新然后全部页面回流重绘, 所以会通过translate3d等方式, 另起一个复合图层, 节省性能(硬件加速)

从Event Loop谈JS的运行机制

读这部分的前提是已经知道了JS引擎是单线程,而且这里会用到上文中的几个概念:(如果不是很理解,可以回头温习)

  • JS引擎线程

  • 事件触发线程

  • 定时触发器线程

然后再理解一个概念

  • JS分为同步任务和异步任务

  • 同步任务都在主线程上执行,形成一个执行栈

  • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。

  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

此时为什么有时候setTimeout推入的事件不能准时执行?因为可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码, 所以自然有误差。

00.png

事件循环机制进一步补充

  • 主线程运行时会产生执行栈, 栈中的代码调用某些api时,它们会在事件队列中添加各种事件(当满足触发条件后,如ajax请求完毕)

  • 而栈中的代码执行完毕,就会读取事件队列中的事件,去执行那些回调

好好.png

单独说说定时器

  1. 计时是由定时器线程控制, 因为js是单线程, 如果阻塞会造成计时不准确
  // 1s后(定时器线程计时), 将回调函数推入事件队列中, 等待主线程执行
  setTimeout(function(){
    console.log('hello!');
  }, 1000);

事件循环进阶:JS两种任务类型 macrotask(宏任务)与microtask(微任务)

Promise里面的新概念: microtask

  1. 宏任务(macrotask): 每次执行栈执行的代码就是一个宏任务(从头到尾执行完为止)

    • 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
  2. 微任务(microtask):在当前 task 执行结束后立即执行的任务(两个宏任务之间)

  3. 例子

    1. macrotask:主代码块,setTimeout,setInterval等(可以看到,事件队列中的每一个事件都是一个macrotask)

    2. microtask:Promise,process.nextTick等

    3. Promise响应速度相比setTimeout快

i.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容