浏览器从下载网页到渲染的过程与 V8 引擎介绍 —— 上篇

1,423 阅读7分钟

最近再次学习了关于浏览器渲染网页的过程,并且新了解到了些关于 Chrome 和 node 中所使用的执行 js 代码的 V8 引擎相关知识,在此做个分享。正文部分总计 3000 字左右,故而拆分成上下两篇,上篇主要介绍浏览器渲染网页的过程,下篇主要介绍 V8 引擎。有不足之处或是任何意见建议,欢迎各位大佬不吝斧正~

网页大致渲染过程

一般情况下,在浏览器输入网址后,首先网络线程会下载一个 index.html 文件,然后就会产生一个渲染任务,传递给渲染主线程的事件队列,最后由渲染主线程执行渲染任务,html 解析器开始解析。

浏览器为了提高解析效率还会开启一个预解析线程,遇到 <link> 标签,就由网络线程去服务器下载对应的 css 文件,然后进行一些解析工作,把结果交给渲染主线程生成 css 样式树,所以 css 不会阻塞 html 的解析。

预解析线程遇到 <script> 标签,也是由网络线程去下载对应的 js 文件,与处理 css 文件不同的是,在这一过程中,渲染主线程会暂停,等待 js 下载完毕,然后启动 V8 引擎执行 js,再继续解析 html。因为 js 可能会修改当前的 dom 树,或是涉及样式的修改,不阻塞 html 解析可能导致无用功。

有一张很经典的图,用于描述浏览器(webkit 内核)的大致渲染过程,我稍做修改,重绘如下:

0.png

  1. 遇见 html 标记,调用 html 解析器构建 dom 树;
  2. 遇见 style / link 标记,调用相应解析器处理 css 标记,构建 css 样式树;
  3. 遇见 script 标记,调用 js 引擎,处理 script 标记、绑定事件、修改 dom 树(也就是上图中紫色椭圆形所代表的含义)或 css 树等;
  4. 将 dom 树和 css 树合并(Attachment)成一个渲染树(Render Tree),此时每个 dom 节点的所有样式都是计算处理过的,比如相对单位(rem)会变成绝对单位(px):

5.png

  1. 根据渲染树来渲染,计算每个节点的几何信息,包括尺寸和位置,生成 layout(布局)树。注意,dom 树和 layout 树不一定会一一对应,因为有些 dom 节点是隐藏的(比如 display: none),那么就不会出现在 layout 树;而有些 dom 节点使用了伪元素选择器(比如 ::before),虽然 dom 树上不会有伪元素节点,但因为它有几何信息,所以 layout 树上会多出一个伪元素的节点;

这里补充说明下,所谓的回流 (Reflow) ,其本质就是重新计算 layout 树。为了避免过于频繁地回流,对于诸如改变某个节点的宽高这种操作,浏览器会进行合并,异步地去完成。但是对于获取布局属性的操作(比如 .clientHeight),浏览器会立即 Reflow 以保证数据的准确性。

  1. 现代浏览器中,渲染主线程会使用一套复杂的策略对 layout 树进行分层(Layer),以便某一层改变时,仅需要对该层进行处理。比如滚动条(下图中的 #301)就会单独分为一层,transformopacitywill-change,也会影响分层结果:

6.png

  1. 接着渲染主线程为每个层单独产生绘制指令集(类似 canvas 的绘制命令),将绘制信息交给合成(Compositor)线程完成,至此渲染主线程的工作就完成了;

所谓的重绘 (Repaint) ,其本质就是重新根据分层信息计算了绘制指令。比如更改了某个节点的颜色,或是布局样式,故而 Reflow 一定会导致 Repaint。

  1. 合成线程会对每个图层进行分块(Tiling),这一步将会从线程池调用多个线程完成;
  2. 之后调用 GPU 进程,将各个块(优先处理靠近视口的块)光栅化(Raster)为位图,再交给合成线程;
  3. 最后进行绘制(Draw),合成线程根据位图信息,生成指引(quad)信息 —— 标识出各个位图应该在屏幕的哪个位置,并考虑旋转、缩放的变形(transform),这也是为何动画使用 transform 效率更高的原因,它与渲染主线程无关 —— 交给 GPU 进程,进而产生系统调用,交给 GPU 硬件完成绘制。

下面对上述中的一些概念做个解释:

浏览器内核

浏览器内核也叫渲染引擎,或是浏览器引擎(browser engine)、排版引擎(layout engine)等。不同的浏览器内核不同,主要有以下这么 4 种:

  • Gecko:早期 Firefox 使用
  • Trident:IE 使用
  • WebKit:Safari 使用
  • Blink:Chrome、Edge 使用,是 WebKit 的分支引擎

HTML 解析器

HTML 解析器是浏览器内核的一个主要模块。一个渲染引擎主要包括 5 大模块:

  • html 解析器:将 html 文本解析并生成一个 dom 树。
  • css 解析器:为 dom 中的各个元素对象计算出样式信息,为布局提供基础设施。
  • js 引擎:执行 js 代码,js 是高级语言,需要通过 js 引擎转成最终的机器指令来执行。
  • 布局(Layout) 模块:将 dom 节点和样式信息结合,计算大小位置等,形成一个能表达这所有信息的内部表示模型。
  • 绘图模块(Painting):使用图形库将布局计算后的各个网页的节点绘制成图像结果。

这 5 大模块依赖很多其它基础模块,比如网络、存储、2d/3d 图像、音/视频/图片解码器等。

注:WebKit 事实上可以分为两部分:WebCore(css、svg、布局、渲染树、html、dom 等) 和 JSCore(js 引擎)。

dom 树

dom 树,即 Document Object Model Tree(文档对象模型树),是浏览器解析 html 文档后生成的树形结构。每个 html 标记、文本节点和属性都作为一个节点(对象)在 dom 树中表示:

document  
└── html  
    ├── head  
    │   ├── meta (charset)  
    │   ├── meta (viewport)  
    │   └── title  
    │       └── "Document"  
    └── body  
        └── h1  
            └── "Hello, World!"

如果我们直接在浏览器控制台打印查看 document ,看不出它的结构:

1.png

可以使用 console.dir(document) 来显示 document 的所有属性和方法:

2.png

css 样式树

一个 html 文档中的样式文件可能来自 <link><style>、内联样式或是浏览器默认样式等,一个样式文件就是一个 CSSStyleSheet,里面有不同的规则 CSSStyleRule,规则中又包含着选择器和样式 style,它们共同组成了 css 样式树:

StyleSheetList  
└── CSSStyleSheet  
    ├── CSSStyleRule  
    │   ├── body
    │   └── style 
    │   └── font-size: 16px
    │       
    └── CSSStyleRule
        └── h1  
        └── style 
            └── color: red

随便打开一个网页,在控制台输入 document.styleSheets 就能查看到该网页的 StyleSheetList 对象,里面有着所有除浏览器默认样式之外的 CSSStyleSheet,包括内联样式和外联样式:

3.png

可以通过 js 控制这些 CSSStyleSheet,比如添加条规则让所有 div 的字体颜色为红色:

document.styleSheets[4].addRule('div', 'color: red!important')

如此,页面就会添加一个样式:

4.png

注意其和直接使用 dom 的 .style 设置样式的区别。

高级语言

上面还提到, js 是高级语言,那么什么是高级语言呢?为什么要用 js 引擎来执行?
编程语言,按照发展历史划分,大致可以分为三个阶段:

  1. 机器语言

就是 010101 这样的机器指令。机器能直接识别而无需经过翻译,每一操作码在计算机内部都有相应的电路来完成。从使用的角度看,机器语言是最低级的语言。

  1. 汇编语言

也叫作符号语言,如 MOV(代表数据传递)、ADD(代表数字逻辑上的加减) 等汇编指令就是汇编语言。属于第二代计算机语言。

  1. 高级语言

c、c++、c#、java 等,js 也是一门高级编程语言。计算机本身并不认识高级语言,所以这些高级语言想要最终在 cpu 运行,最终需要转换成机器指令。

image.png

js 引擎

我们知道 .js 文件可以通过浏览器或 node 执行,其实最后,都是被 cpu 执行。cpu 只认识自己的指令集,是机器语言,所以需要 js 引擎将 js 文件翻译成 cpu 指令来执行。js 引擎有好几种,比如第一款 js引擎,由 js 作者 Brendan Eich 开发的 SpiderMonkey;webkit 和小程序中使用的 js 引擎,由苹果公司开发的 JavaScriptCore 和下篇主要介绍的由谷歌开发,在 Chrome 中使用的 V8 引擎等。

感谢.gif
点赞.png