开发者路线图详解-浏览器运行机制?

想要查看前面的笔记请翻阅我的CSDN博客,作者码字不易,喜欢的话点赞,加个关注吧,后期还有很多干货等着你!

在这里插入图片描述
在这里插入图片描述

运行组成部分

在这里插入图片描述

主进程(Browaer进程)

只有一个,负责浏览器的页面展示,用户交互,负责页面管理,创建或者销毁其他进程,网络资源管理以及下载,渲染页面参数到用户界面上

第三方插件进程

每一个插件在加载使用的时候,都会创建一个对应的进程

GPU进程

只有一个,用于3d图形的绘制

浏览器渲染进程(Renderer 进程,内部是多线程的)

每一个Tap页面都会有一个渲染进程,相互不影响,主要用于页面渲染,脚本执行,事件处理。
Chrom浏览器

思考:为什么要使用多进程?

避免单个 page crash 影响整个浏览器,避免第三方插件 crash 影响整个浏览器,多进程充分利用多核优势,方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

简单点理解:如果浏览器是单进程,那么某个 Tab 页崩溃了,就影响了整个浏览器,体验有多差;同理如果是单进程,插件崩溃了也会影响整个浏览器;而且多进程还有其它的诸多优势,当然内存等资源消耗也会更大

渲染进程介绍–浏览器内核

提到浏览器里面的进程,最大的功臣可能就是渲染进程了,她负责了最直观的用户体验和感受,它也是很复杂的一个进程,是由多个线程组成。所以我们主要介绍它。

渲染进程–GUI渲染线程

负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等。
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

注意,GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。
为什么互斥:由于 JS 是可以操作 DOM 的,如果同时修改元素属性并同时渲染界面(即 JS 线程和 UI 线程同时运行), 那么渲染线程前后获得的元素就可能不一致了(简单说就是 js 修改 dom 后没有重新渲染成功)

渲染进程–JS引擎线程

也称为 JS 内核,负责处理 Javascript 脚本程序。(例如 大名鼎鼎的V8 引擎)
JS 引擎线程负责解析 Javascript 脚本,运行代码。
JS 引擎一直等待着任务队列中任务的到来,然后加以处理,一个 Tab 页(renderer 进程)中无论什么时候都只有一个 JS 线程在运行 JS 程序

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

渲染进程–事件触发线程

归属于浏览器而不是 JS 引擎,用来控制事件循环(可以理解,JS 引擎自己都忙不过来,需要浏览器另开线程协助)
当 JS 引擎执行代码块如 setTimeOut 时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX 异步请求等),会将对应任务添加到事件线程中
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理

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

渲染进程–定时触发器线程

传说中的 setInterval 与 setTimeout(这些 API 却不是引擎提供的而是浏览器提供的 Web API,比如说 DOM、AJAX、setTimeout) 所在线程浏览器定时计数器并不是由 JavaScript 引擎计数的,(因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待 JS 引擎空闲后执行)

注意,W3C 在 HTML 标准中规定,规定要求 setTimeout 中低于 4ms 的时间间隔算为 4ms。

渲染进程–异步 http 请求线程

在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求
将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由 JavaScript 引擎执行。

进程间的通讯

  1. 主进程(Browser进程)收到用户请求,首先需要获取页面内容(比如通过网络下载资源),随后将该任务通过RendererHost接口传递到渲染进程(Render进程)
  2. 渲染进程(Render进程)的Renderer接口接受到信息,简单解释后交给渲染线程,开始渲染
  3. 渲染线程接收请求,加载网页并渲染网页,这其中可能需要 Browser 进程获取资源和需要 GPU 进程来帮助渲染
  4. 当然可能会有 JS 线程操作 DOM(这样可能会造成回流并重绘)
  5. 最后渲染进程(Render进程)将结果传递给主进程(Browser进程)
  6. 渲染进程(Render进程)接到结果并将结果绘制出来
    在这里插入图片描述

一些渲染进程的注意事项(Render进程)

GUI 渲染线程与 JS 引擎线程互斥导致JS 阻塞页面加载

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JS 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JS 引擎为互斥的关系,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新则会被保存在一个队列中等到 JS 引擎线程空闲时立即被执行。从上述的互斥关系,可以推导出,JS 如果执行时间过长就会阻塞页面。譬如,假设 JS 引擎正在进行巨量的计算,此时就算 GUI 有更新,也会被保存到队列中,等待 JS 引擎空闲后执行。然后,由于巨量计算,所以 JS 引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。所以,要尽量避免 JS 执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

JS异步实现逻辑

首先,需要明确一点的是,js 引擎是单线程的,当引擎需要处理耗时的操作时,会将其处理为异步操作,它们包括 键盘,鼠标I/O输入输出时间、窗口大小的resize事件、定时器(setTimeout、setIneterval)事件、Ajax事件等。当这些异步事件发生的时候,他们将会被放置在浏览器的任务列队中,当JS运行执行线程空闲的时候,会按照列队的方式一一执行。
在这里插入图片描述

宏任务与微任务

宏任务(macrotask):

  • 主代码块和任务队列中的回调函数就是宏任务。
  • 为了使js内部宏任务和DOM任务能够有序的执行,每次执行完宏任务后,会在下一个宏任务执行之前,对页面重新进行渲染。(宏任务 -> 渲染 -> 宏任务)

微任务(microtask):

  • 在宏任务执行过程中,执行到微任务时,将微任务放入微任务队列中。
  • 在宏任务执行完后,在重新渲染之前执行。
  • 当一个宏任务执行完后,他会将产生的所有微任务执行

分别在什么场景下会产生宏任务或微任务呢:
宏任务:主代码块,setTimeout,setInterval(任务队列中的所有回调函数都是宏任务)
微任务:Promise

浏览器渲染流程

  • 浏览器将域名利用操作系统去访问DNS服务器换取IP地址
  • 向IP发送http请求
  • 服务器收到请求,响应
  • 浏览器获得到返回
  • 解析内容建立Rendering Tree
  • 在这里插入图片描述

建立Rendering Tree流程

  1. 解析 HTMl 构建 dom(解析过程:浏览器会自动把 HTML 文档解析为一个“文档对象模型”,即 Document Object Model,简称 DOM,这是一个树形结构,树根是 Document 对象,树干是网页的根元素<html>,然后分出两个枝丫,一个是<head>,一个是<body>,然后网页上的其他标签就是这棵树上的树叶和树枝了,通过这个结构,就可以查找和控制网页上的任何一个元素了。因此,可以这么说,网页上的任何元素都是 Document 对象的子对象)
    在这里插入图片描述

  2. 解析 CSS 产生 CSS 规则树(解析过程:css 是由单独的下载线程异步下载的,本身不会阻塞 Dom 加载,它和 DOM 结构比较像然后结合 DOM 生成 RenderTree)

  3. Javascript 解析(解析过程:通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。)

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

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

  6. 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成(composite),显示在屏幕上
    在这里插入图片描述

在这里插入图片描述
页面加载时,浏览器先将HTML代码解析成dom树,再把样式解析成样式结构体,然后将二者结合构建成render tree使得每一个节点都有自己的样式

一些渲染过程中的注意事项

DOMContentLoaded 与 onload的区别

DOMContentLoaded :当 DOMContentLoaded 事件触发时,仅当 DOM 加载完成,不包括样式表,图片
onload:当 onload 事件触发时,页面上所有的 DOM,样式表,脚本,图片都已经加载完成了。 (渲染完毕了)
触发顺序:DOMContentLoaded 先 onload 后

怎样加快渲染速度?

上面说过 GUI 渲染线程与 JS 引擎线程是互斥的,所以渲染过程中,如果遇到<script>就停止渲染,执行 JS 代码,也就是说,在构建 DOM 时,HTML 解析器若 遇到了 JavaScript,那么它会暂停构建 DOM,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复 DOM 构建。那么首屏想要加载快,就不应该加载js,这也是都建议将 script 标签放在 body 标签底部的原因

怎样延时使用JS脚本?

<script src="script.js"></script>

没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

<script defer src="script.js"></script>(延迟执行)

defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。

<script async src="script.js"></script> (异步下载)

加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
async 属性表示异步执行引入的 JavaScript

拓展:defer 和 async 总结

  • defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  • defer的script.js 的执行要在所有元素解析完成之后
  • async如果已经加载好,就会开始执行。也就是加载不阻塞,执行会阻塞。
  • 在加载多个 JS 脚本的时候,async 是无顺序的加载,而 defer 是有顺序的加载。
  • async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的
    在这里插入图片描述
    蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

浏览器的回流与重绘 (Reflow & Repaint)

回流

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的 DOM 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法

重绘

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘

回流与重绘会导致什么

根据Opera列出“reflow和repaint是减缓JavaScript的三大主要原因之一”一文我们可知,回流和重绘对性能的影响。当一个元素的外观的可见性visibility发生改变的时候,重绘(repaint)也随之发生,但是不影响布局。类 似的例子包括:outline, visibility, or background color。根据Opera浏览器,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。而回流更是性能的关键因为其变化涉及到部分 页面(或是整个页面)的布局。一个元素的回流导致了其所有子元素以及DOM中紧随其后的祖先元素的随后的回流。

如何避免回流

CSS

  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
  • 避免使用 CSS 表达式(例如:calc())。

Javascript

  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性。
  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
  • 也可以先为元素设置 display: none,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流

Guess you like

Origin blog.csdn.net/weixin_42842069/article/details/117404076