React前端入门培训指南02——浏览器工作原理

 读书笔记  原创  管理员  2020-03-03 19:30

概要:WebKit是一种用来让网页浏览器绘制网页的排版引擎。它被用于Apple Safari。其分支Blink被用于基于Chromium的网页浏览器,如:Opera与Google Chrome。WebKit的HTML及JavaScript代码源自KDE的KHTML及KJS库的一个分支,现已由KDE、Apple、Google、Nokia、Bitstream、BlackBerry及Igalia等独立开发。2013年4月3日,Google宣布它创建了WebKit中WebCore组件的分支——Blink,Blink将用于新版Google Chrome与Opera。

浏览器市场份额

全球

https://gs.statcounter.com/browser-market-share

中国

https://gs.statcounter.com/browser-market-share/all/china

WebKit(Blink)

WebKit是一种用来让网页浏览器绘制网页的排版引擎。它被用于Apple Safari。其分支Blink被用于基于Chromium的网页浏览器,如:Opera与Google Chrome。

WebKit的HTML及JavaScript代码源自KDE的KHTML及KJS库的一个分支,现已由KDE、Apple、Google、Nokia、Bitstream、BlackBerry及Igalia等独立开发。

2013年4月3日,Google宣布它创建了WebKit中WebCore组件的分支——Blink,Blink将用于新版Google Chrome与Opera。

浏览器的架构

User Interface:浏览器界面,如地址栏、菜单工具栏、前进、后退、主页按钮等界面部分。

Browser engine:浏览器引擎,负责User Interface向Rendering engine指令的传输。

Rendering engine:渲染引擎,负责具体网页页面的呈现。

Networking:网络,负责网络的调用,如发送http请求,获取远程资源等。

JavaScript Interpreter:JavaScript解释器,用于解析和执行JavaScript脚本。

UI Backend:用户界面后端,用于绘制基本的窗口小组件,比如:select下拉框。

Data Persistence:数据存储,负责持久化数据的存储与读取,如cookie的获取与保存、IndexedDB等。

DOM 和 CSSOM

DOM

浏览器提供给JavaScript调用的用于操作DOM节点的接口。

CSSOM

浏览器提供给JavaScript调用的用于操作CSS节点的接口。

渲染引擎(Render)

渲染引擎的目的就是将CSS、HTML、图片渲染成用户可以看到的网页,当然也可以通过一些插件显示pdf文件等其它功能。

基本流程

  1. 解析HTML,生成DOM树,同时也会解析CSS,生成CSS规则等
  2. 根据DOM树与CSS规则生成渲染树,渲染树包含颜色、尺寸大小等视觉属性。
  3. 对渲染树进行布局计算,确定元素在界面上对应的精确坐标
  4. 在界面上绘制渲染树,从而显示出我们可以看到的页面

WebKit主要流程

Gecko主要流程

解析(Parse)

词法分析

将输入的内容分割成为大量标记的过程。这里的标记是指语言中的词汇,比如字典中的单个字。

语法分析

根据语法规则来分析文档结构的过程。

HTML解析器(HTML Parser)

目的是将 HTML 标记解析为解析树。HTML 的定义采用了 DTD 格式。它包括所有允许使用的元素及其属性和层次结构的定义。DTD 存在一些变体,严格模式完全遵守 HTML 规范,而其它模式可支持以前的浏览器所使用的标记。这样做的目的是确保向下兼容一些早期版本的内容。

标记化

也就是词法分析的过程,将HTML解析成多个标记,包括:开始标记、结束标记、属性名、属性值。

该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前的标记化状态和树结构状态会影响进入下一状态的决定。这意味着,即使接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。

示例:

<html>
  <body>
    Hello world
  </body>
</html>


初始状态是数据状态。过程分析:

  1. 遇到字符 < 时,状态更改为“标记打开状态”。
  2. 接收一个 a-z 字符会创建“起始标记”,状态更改为“标记名称状态”。
  3. 遇到 > 标记时,会发送当前的标记,状态改回“数据状态”。
  4. <body> 标记也会进行同样的处理。
  5. 接收到 Hello world 中的 H 字符时,将创建并发送字符标记,直到接收 </body> 中的 <。我们将为 Hello world 中的每个字符都发送一个字符标记。
  6. 接收下一个输入字符 / 时,会创建 end tag token 并改为“标记名称状态”。我们会再次保持这个状态,直到接收 >。然后将发送新的标记,并回到“数据状态”。</html> 输入也会进行同样的处理。

树的构建

标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。

这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。

示例:

<html>
  <body>
    Hello world
  </body>
</html>


初始模式是“initial mode”。过程分析:

  1. 接收 HTML 标记后转为“before html”模式,并在这个模式下重新处理此标记。这样会创建一个 HTMLHtmlElement 元素,并将其附加到 Document 根对象上。
  2. 然后状态将改为“before head”。即使我们的示例中没有“head”标记,系统也会隐式创建一个 HTMLHeadElement,并将其添加到树中。
  3. 现在我们进入了“in head”模式,然后转入“after head”模式。系统对 body 标记进行重新处理,创建并插入 HTMLBodyElement,同时模式转变为“in body”。
  4. 现在,接收由“Hello world”字符串生成的一系列字符标记。接收第一个字符时会创建并插入“Text”节点,而其他字符也将附加到该节点。
  5. 接收 body 结束标记会触发“after body”模式。现在我们将接收 HTML 结束标记,然后进入“after after body”模式。
  6. 接收到文件结束标记后,解析过程就此结束。

DOM树(DOM Tree)

HTML解析器生成的解析树就是由DOM元素和属性构成的树结构。DOM是外部内容(如:JavaScript)操作HTML元素的接口。比如,使用JS操作DOM对象中的属性后,最终浏览器再根据DOM对象的变化更新界面中该DOM的效果显示。

HTML标记与DOM树基本上是一 一对应:

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>


与之对应的DOM树对象结构为:

CSS解析器(CSS Parser)

CSS使用的是上下文无关的语法,因此可以使用一套具体的严格的语法规则来解析它。

CSS 模块在实现上的特点:CSS 对象众多、计算非常频繁(需要为每个元素节点计算样式)。如何高效的计算样式是浏览器内核的重点也是难点。

CSS结构:

解析过程和解析HTML时类似:

WebKit采用的是:Flex 和 Bison,它会将CSS文件解析成一个【StyleSheet】对象,这个对象中包含选择器和其它CSS对象信息。

【StyleSheet】对象:

StyleResolver

StyleResolver 负责将 CSS 转成 RenderStyle对象。StyleResolver 类根据元素的信息,例如标签名和类别等,保存到新建的 RenderStyle 对象中,它们最后被 RenderObject 类所管理和使用。

渲染树(Render Tree)

DOM 树构建的同时,渲染引擎也在构建渲染树。

渲染树是由可视化元素按照先后排列顺序组成的树,它需要保证最终界面元素显示的正确性。

DOM树与CSS规则经过处理后,就形成渲染树,渲染树上的每个节点都是一个基于RenderObject的对象。

DOM 树与 CSS 规则结合并创建渲染树的过程叫做附加(Attachment),每一个节点都会有一个 attach 方法,每当节点插入至 DOM 树时,就调用节点的 attach 方法创建渲染树对象。

基类RenderObject的大致结构如下:

class RenderObject{
  virtual void layout();//布局计算
  virtual void paint(PaintInfo);//用于后续的绘制流程
  virtual void rect repaintRect();//矩形区域信息
  Node* node;  //DOM 节点
  RenderStyle* style;  //计算后的样式信息
  RenderLayer* containgLayer; //z-index层信息
}

 

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;
    switch (style->display()) {//获取display样式属性
        case NONE://如果此节点为隐藏时,则不用生成RenderObject对象
            break;
        case INLINE://生成一个inline形式的对象
            o = new (arena) RenderInline(node);
            break;
        case BLOCK://生成一个block形式的对象
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK://生成一个inline-block形式的对象
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM://生成一个list-item形式的对象
            o = new (arena) RenderListItem(node);
            break;
       ...
    }
    return o;

}

 

渲染引擎会针对不同的样式规则,可能会创建不同的RenderObject对象:

DOM树与渲染树的对应关系:

DOM树与渲染树并不是一 一对应关系,并且渲染树上只会显示需要显示的区域元素,如果DOM被设置为display:none,则此元素不会出现在渲染树上。

布局(Layout)

渲染树上的节点并不包含元素最终在页面上显示的位置与大小信息。因为还需要经过布局计算才能得到最终结果。

HTML 采用基于流的布局模型,排列顺序按从上至下,从左到右。一般来说,位于下游的元素不会影响到上游元素的位置与几何信息。

布局计算是一个递归的过程,从渲染树的顶部开始,直到计算完所有元素的几何位置信息。

文档流

Dirty 标记

重绘重排是一个非常消耗计算资源的过程,为了避免没有意义的布局计算,浏览器会采用Dirty标记模式,来准确地对某个发生变更的渲染树节点进行重新布局。

有两种标记方式:节点本身需要重新布局(包括此节点与此节点的所有子元素)、仅节点的子元素需要重新布局

布局方式

全量布局:所有的元素都需要重新渲染,比如页面的全局样式发生了修改。

增量布局:只对有Dirty标记的元素进行重新布局,此过程是异步进行的。

处理流程

  1. 父呈现器确定自己的宽度。
  2. 父呈现器依次处理子呈现器,并且:放置子呈现器(设置 x,y 坐标)。
  3. 如果有必要,调用子呈现器的布局(如果子呈现器是 dirty 的,或者这是全局布局),此时会计算子呈现器的高度。
  4. 父呈现器根据子呈现器的累加高度以及边距和补白的高度来设置自身高度,此值也可供父呈现器的父呈现器使用。
  5. 将其 dirty 位设置为 false。

注意:如果呈现器在布局过程中需要换行,会立即停止布局,并告知其父代需要换行。父代会创建额外的呈现器,并对其调用布局。

计算过程

RenderLayer Tree

浏览器渲染引擎并不是直接使用 Render 树进行绘制,为了方便处理 Positioning(定位),Clipping(裁剪),Overflow-scroll(页內滚动),CSS Transform/Opacity/Animation/Filter,Mask or Reflection,Z-indexing(Z排序)等,浏览器需要生成另外一棵树 – Layer 树。

渲染引擎会为一些特定的 RenderObject 生成对应的 RenderLayer,而这些特定的 RenderObject 跟对应的 RenderLayer 就是直属的关系,相应的,它们的子节点如果没有对应的 RenderLayer,就从属于父节点的 RenderLayer。最终,每一个 RenderObject 都会直接或者间接地从属于一个 RenderLayer。

浏览器渲染引擎遍历 Layer 树,访问每一个 RenderLayer,再遍历从属于这个 RenderLayer 的 RenderObject,将每一个 RenderObject 绘制出来。读者可以认为,Layer 树决定了网页绘制的层次顺序,而从属于 RenderLayer 的 RenderObject 决定了这个 Layer 的内容,所有的 RenderLayer 和 RenderObject 一起就决定了网页在屏幕上最终呈现出来的内容。

绘制(Paint)

系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。

和布局类似,绘制的类型也分为全局绘制与增量绘制。

发生修改后的呈现器将其在屏幕上对应的矩形区域设为无效,这导致 OS 将其视为一块“dirty 区域”,并生成“paint”事件。

在重新绘制之前,WebKit 会将原来的矩形另存为一张位图,然后只绘制新旧矩形之间的差异部分。

资源(Resource)

脚本和样式表的处理顺序

脚本

默认都是同步加载的,当加载到此脚本时,浏览器立即停止对文档的解析,并开始处理脚本。

可以设置defer和async来优化加载过程。

预解析

在处理执行脚本时,浏览器会使用其它线程来扫描并解析文档的其它部分,找出需要加载的外部资源,以提高并行加载资源的能力。

注意:这里只会解析外部资源的引用,不会引发DOM的修改。因为此时主解析器正在处理被阻塞的脚本。

样式表

当浏览器加载脚本时,发现此脚本依赖某个样式信息(比如获取或设置某个元素的样式时),此时,浏览器会阻止此脚本的加载而去优先加载样式表信息。

样式表加载完成后,再处理此脚本,以此来保证脚本操作的样式信息是正确的。

JavaScript引擎

编译与执行过程

编译器:将源码转为AST,有些引擎会直接将AST再转为字节码。

解释器:解释执行接收到的字节码。

JIT:将字节码或AST转为本地机器码。

垃圾回收器:负责垃圾回收和收集引擎中的相关信息,以此提高性能。

与渲染引擎的关系

参考资料

浏览器原理:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

CSS 解析原理:http://jartto.wang/2017/11/13/Exploring-the-principle-of-CSS-parsing/


浏览器  原理  web  

编辑:myweb   最后更新于:2020-03-06 20:54




声明:本站部分文章系本站编辑转载,转载目的在于加快信息的传递,及时与广大网友分享更多信息,并不代表本站赞同其观点和对其真实性负责。 如涉及作品内容、版权和其它问题,请及时与本站联系,我们将在第一时间删除内容!本站文章版权归原作者所有,内容为作者个人观点,本站只提供参考并不构成任何投资及应用的建议。


联系我:x889@foxmail.com,鄂ICP备14016278号-2
©2016-2020 我的ABC All Rights Reserved.
友情链接: 一起编程网