阅读 70

浏览器相关总结

主流浏览器

浏览器shell内核
IEtrident
FirefoxGecko
Google chromeWebkit/blink
SafariWebkit
Operapersto

浏览器的高层结构

浏览器的主要组件为:

  1. 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
  2. 浏览器引擎 - 在用户界面和呈现引擎之间传送指令。
  3. 呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
  4. 网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
  5. 用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
  6. JavaScript 解释器。用于解析和执行 JavaScript 代码。
  7. 数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

资源的整个渲染流程

webkit的渲染主流程

Gecko的渲染主流程

解析(迭代过程)

词法分析

将输入内容分割成大量标记的过程。标记是语言中的词汇,即构成内容的单位。在人类语言中,它相当于语言字典中的单词。

语法分析

应用语言的语法规则的过程。

解析器通常将解析工作分给以下两个组件来处理:词法分析器(有时也称为标记生成器),负责将输入内容分解成一个个有效标记;而解析器负责根据语言的语法规则分析文档的结构,从而构建解析树。词法分析器知道如何将无关的字符(比如空格和换行符)分离出来。

解析是一个迭代的过程。通常,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。如果发现了匹配规则,解析器会将一个对应于该标记的节点添加到解析树中,然后继续请求下一个标记。

如果没有规则可以匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与所有内部存储的标记匹配的规则。如果找不到任何匹配规则,解析器就会引发一个异常。这意味着文档无效,包含语法错误。

上下文无关文法

一个内容推导出来的结果,不依赖任何其他的内容

具体解释请看这里

解析算法

我们在之前章节已经说过,HTML 无法用常规的自上而下或自下而上的解析器进行解析。

原因在于:

  1. 语言的宽容本质。
  2. 浏览器历来对一些常见的无效 HTML 用法采取包容态度。
  3. 解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在 HTML 中,脚本标记如果包含 document.write,就会添加额外的标记,这样解析过程实际上就更改了输入内容。

此算法由两个阶段组成:标记化和树构建

标记化是词法分析过程,将输入内容解析成多个标记。

HTML 标记包括起始标记、结束标记、属性名称和属性值。

标记算法

  • '<' 标记打开状态
  • 遇到元素名 标记名状态
  • '>' 数据状态

树构建算法

在创建解析器的同时,也会创建 Document 对象。在树构建阶段,以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。

标记生成器识别标记,传递给树构造器。然后数构造器会根据规范中定义了每个标记所对应的 DOM 元素构建dom。

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

解析时的状态模式:initial mode(当接收第一个标记的之前) ----> before(接收到标记,即<) -----> in(处于标记元素中) ----> after(接收到结束标记>) ----> after after(当遇到</html>) ----> 解析完毕

此时就开始执行那些带有defer属性的脚本

浏览器的容错机制

嵌套表格的处理。WebKit 使用一个堆栈来保存当前的元素内容,它会从外部表格的堆栈中弹出内部表格。现在,这两个表格就变成了同级关系。

嵌套表单的处理。第二个表单直接被忽略。

过多相同标记的嵌套。我们只允许最多 20 层同类型标记的嵌套,如果再嵌套更多,就会全部忽略。

css解析

Bison解析生成器: 会创建自下而上的移位归约解析器。

CSS 规则对象则包含选择器和声明对象(样式属性),以及其他与 CSS 语法对应的对象。

预解析

预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。

呈现树构建

这是由可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的作用是让您按照正确的顺序绘制内容。

每一个呈现器都代表了一个矩形的区域,通常对应于相关节点的 CSS 框。它包含诸如宽度、高度和位置等几何信息。

样式计算

构建呈现树时,需要计算每一个呈现对象的可视化属性。这是通过计算每个元素的样式属性来完成的。

样式计算存在以下难点:

  1. 样式数据是一个超大的结构,存储了无数的样式属性,这可能造成内存问题。
  2. 如果不进行优化,为每一个元素查找匹配的规则会造成性能问题。要为每一个元素遍历整个规则列表来寻找匹配规则,这是一项浩大的工程。选择器会具有很复杂的结构,这就会导致某个匹配过程一开始看起来很可能是正确的,但最终发现其实是徒劳的,必须尝试其他匹配路径。

这些难点不同浏览器的处理各不相同。

布局

呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。

布局是一个递归的过程。它从根呈现器(对应于 HTML 文档的 `` 元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。

Dirty 位系统

为避免对所有细小更改都进行整体布局,浏览器采用了一种“dirty 位”系统。如果某个呈现器发生了更改,或者将自身及其子代标注为“dirty”,则需要进行布局。

有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代需要布局。

全局布局和增量布局

全局布局是指触发了整个呈现树范围的布局,触发原因可能包括:

  1. 影响所有呈现器的全局样式更改,例如字体大小更改。
  2. 屏幕大小调整。

布局可以采用增量方式,也就是只对 dirty 呈现器进行布局(这样可能存在需要进行额外布局的弊端)。 当呈现器为 dirty 时,会异步触发增量布局。例如,当来自网络的额外内容添加到 DOM 树之后,新的呈现器附加到了呈现树中。

异步布局和同步布局

增量布局是异步执行的。Firefox 将增量布局的“reflow 命令”加入队列,而调度程序会触发这些命令的批量执行。WebKit 也有用于执行增量布局的计时器:对呈现树进行遍历,并对 dirty 呈现器进行布局。 请求样式信息(例如“offsetHeight”)的脚本可同步触发增量布局。 全局布局往往是同步触发的。 有时,当初始布局完成之后,如果一些属性(如滚动位置)发生变化,布局就会作为回调而触发。

优化

如果布局是由“大小调整”或呈现器的位置(而非大小)改变而触发的,那么可以从缓存中获取呈现器的大小,而无需重新计算。 在某些情况下,只有一个子树进行了修改,因此无需从根节点开始布局。这适用于在本地进行更改而不影响周围元素的情况,例如在文本字段中插入文本(否则每次键盘输入都将触发从根节点开始的布局)。

定位方案

有三种定位方案:

  1. 普通:根据对象在文档中的位置进行定位,也就是说对象在呈现树中的位置和它在 DOM 树中的位置相似,并根据其框类型和尺寸进行布局。
  2. 浮动:对象先按照普通流进行布局,然后尽可能地向左或向右移动。
  3. 绝对:对象在呈现树中的位置和它在 DOM 树中的位置不同。

分层展示

这是由 z-index CSS 属性指定的。它代表了框的第三个维度,也就是沿“z 轴”方向的位置。

这些框分散到多个堆栈(称为堆栈上下文)中。在每一个堆栈中,会首先绘制后面的元素,然后在顶部绘制前面的元素,以便更靠近用户。如果出现重叠,新绘制的元素就会覆盖之前的元素。 堆栈是按照 z-index 属性进行排序的。具有“z-index”属性的框形成了本地堆栈。视口具有外部堆栈。

重新渲染

如果在操作 DOM 时涉及到元素、样式的修改,就会引起渲染引擎重新计算样式生成 CSSOM 树,同时还有可能触发对元素的重新排布(简称“重排”)和重新绘制(简称“重绘”)。

重排(回流)

可能会影响到其他元素排布的操作。继而引起重绘,例如

  • 修改元素边距、大小

  • 添加、删除元素

  • 改变窗口大小

重绘

只是影响元素的外观,风格,而不会影响布局的。例如

  • 设置背景图片

  • 修改字体颜色

  • 改变 visibility 属性值

如何高效操作dom

  • 在循环外操作元素

  • 批量操作元素

  • 缓存元素集合

  • 尽量不要使用复杂的匹配规则和复杂的样式,从而减少渲染引擎计算样式规则生成 CSSOM 树的时间;

  • 尽量减少重排和重绘影响的区域;

  • 使用 CSS3 特性来实现动画效果。

  • 避免频繁操作样式,可汇总后统一一次修改

  • 尽量使用 class 进行样式修改,而不是直接操作样式

  • 减少 DOM 的操作,可使用字符串一次性插入

总结

html到dom的流程

  • 浏览器接收到http的响应报文是字节码,染后浏览器通过**“编码嗅探算法”**来确定字符编码,将字节流数据节码,从而生成我们人类可认的代码

  • 输入流预处理

    通过上一步解码得到的字符流数据在进入解析环节之前还需要进行一些预处理操作。比如将换行符转换成统一的格式,最终生成规范化的字符流数据,这个把字符数据进行统一格式化的过程称之为“输入流预处理”。

  • 令牌化

    • 将字符数据转化成令牌(Token)
  • 解析 HTML 生成 DOM 树

    如果遇到的是内联代码,也就是在 script 标签中直接写代码,那么解析过程会暂停,执行权限会转给 JavaScript 脚本引擎,待 JavaScript 脚本执行完成之后再交由渲染引擎继续解析。

dom树的解析过程

css到CSSOM 树

就是根据选择器和样式结构构建一颗树。

dom树和css树构建完毕后,浏览器就开始渲染页面了。

构建渲染树

DOM 树包含的结构内容与 CSSOM 树包含的样式规则都是独立的,为了更方便渲染,先需要将它们合并成一棵渲染树。

这个过程会从 DOM 树的根节点开始遍历,然后在 CSSOM 树上找到每个节点对应的样式。

布局

生成了渲染树之后,就可以进入布局阶段了,布局就是计算元素的大小及位置。

布局完成后会输出对应的“盒模型”,它会精确地捕获每个元素的确切位置和大小,将所有相对值都转换为屏幕上的绝对像素。

绘制

绘制过程中的第一步就是遍历布局树,生成绘制记录,然后渲染引擎会根据绘制记录去绘制相应的内容。

参考文献

浏览器的工作原理:新式网络浏览器幕后揭秘

如何高效的操作dom元素

浏览器如何渲染页面

css加载会造成阻塞吗?

  1. css加载不会阻塞DOM树的解析
  2. css加载会阻塞DOM树的渲染
  3. css加载会阻塞后面js语句的执行、

因此,为了避免让用户看到长时间的白屏时间,我们应该尽可能的提高css加载速度,比如可以使用以下几种方法:

  1. 使用CDN(因为CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
  2. 对css进行压缩(可以用很多打包工具,比如webpack,gulp等,也可以通过开启gzip压缩)
  3. 合理的使用缓存(设置cache-control,expires,以及E-tag都是不错的,不过要注意一个问题,就是文件更新后,你要避免缓存而带来的影响。其中一个解决防范是在文件名字后面加一个版本号)
  4. 减少http请求数,将多个css文件合并,或者是干脆直接写成内联样式(内联样式的一个缺点就是不能缓存)
文章分类
前端
文章标签