跳到主要内容

1.1-浏览器工作原理

Create by fall on 2020 ---- Recently revised in 07 Aug 2023

浏览器工作原理

浏览器的组成

  • 人机交互部分(UI)
  • 网络请求部分(Socket 从网络中请求资源)
  • javascript 引擎部分(解析执行 javascript 脚本)
  • 渲染部分(渲染 HTML、CSS)
  • 数据存储部分(cookie、HTML5 中本地存储、localstorage、sessionStorage)

服务器负责找到文件,并将文件的数据返回给浏览器

当静态页面改变时,不用重启服务器就可以实现静态页面的重新加载,因为只是对于网页的引用。

网络请求部分

请求和响应报文

RequestResponses
请求行响应行
请求报文头HTTP响应报文头
(空一行)(空一行)
请求报文体请求报文体

请求行:请求方法,请求路径,请求所使用的 HTTP 协议

响应行:HTTP协议,协议版本,HTTP状态码,状态码对应的消息

脚本执行

  • 每个渲染进程都有一个主线程,主线程会处理 DOM,计算样式,处理布局,JavaScript 任务以及各种输入事件;
  • 维护一个消息队列,新任务(比如 IO 线程)添加到队列尾部,主线程循环地从消息队列头部读取任务,执行任务;
  • 解决处理优先级高的任务:消息队列的中的任务称为宏任务,每个宏任务中都会包含一个微任务队列,在执行宏任务的过程中,如果 DOM 有变化,将该变化添加到微任务队列中;

计算机语言可以分为两种:编译型和解释型语言。

解释型语言

JavaScript 是解释型语言,是在程序运行时通过解释器对程序进行动态解释和执行,又比如 Python。

解释型语言解释过程:解释器会对代码进行词法分析、语法分析,并生产抽象语法树(AST),不过它会再基于抽象语法树生成字节码,最后根据字节码执行程序;

AST 的生成:第一阶段是词法分析,将一行行源码拆解成一个个 token(语法上不可再分、最小单个字符)。第二阶段是解析(语法分析),将上一步生成的 token 数据,根据语法规则转为 AST,这一阶段会检查语法错误;

解释器 ignition 在解释执行字节码,同时会收集代码信息,发现某一部分代码是热点代码(HotSpot),编译器把热点的字节码转化为机器码,并保存起来,下次使用;

字节码存在的意义:直接将 AST 转化为机器码,执行效率是非常高,但是消耗大量内存,从而先转化为字节码解决内存问题;

编译型语言

编译型语言经过编译器编译后保留机器能读懂的二进制文件,比如 C/C++,go 语言。

  • 预处理:展开头文件、宏替换,去掉注释,条件编译
  • 编译:检查语法,生成汇编
  • 汇编:把生成的汇编文件汇编成机器码
  • 链接: 是把各个模块的机器码和依赖库串连到一起生成可执行程序 a.out

浏览器渲染

主流渲染引擎

渲染引擎又叫排版引擎或浏览器内核

  • Chrome 浏览器:Blink 引擎(WebKit 的一个分支)
  • Safari 浏览器:WebKit 引擎(苹果已于 2012 年 7 月 25 日停止开发 Windows 版的 Safari)
  • FireFox 浏览器:Gecko引擎。
  • 0pera 浏览器:Blink引擎(早期使用 Presto 引擎)。
  • Internet Explorer 浏览器:Trident 引擎。
  • Microsoft Edge 浏览器:Blink 引擎

浏览器会解析三类内容:

  • HTML / SVG / XHTML,描述一个页面的结构,浏览器会把 HTML 结构字符串转换成 DOM 树形结构
  • CSS,解析 CSS 生成 CSS 规则树
  • javascript 脚本,加载后,通过 DOM API 和 CSS API 操作 DOM 树和 CSS 树

浏览器工作流程(如何画出整个页面):构建 DOM -> 构建 CSSOM -> 构建渲染树 -> 布局 -> 绘制。

构建 DOM

  • 数据以字节的方式在网络传输(HTTP2 使用 2 进制字节进行传输),从服务器传输到本地后,字节数据根据指定编码转化为字符串(网络进程加载和解析并行)

  • 字符串转换为 Token(标记,例如:<html><body> 等,给出信息的格式<meta><span>),一边解析字符串生成 token,一边消耗 token 生成Node(节点对象),再转化为 DOM。

  • 生成 DOM 树后,根据 CSS 样式表计算出 DOM 树所有节点样式;

构建 CSSOM

  • CSSOM 的构建和 DOM 的构建是一般并行执行的。
  • CSS 的解析方式类似于 DOM,字节数据 -> 字符串 -> Token(标记)-> Node(节点对象)-> 再转化为 CSSOM。
  • 如果 CSS 文件还未下载解析完成,会阻塞渲染(合成布局树需要 CSSOM 和 DOM),只有当 CSSOM 构建完毕后才会进入下一个阶段构建渲染树。
  • CSSOM 会提供 JavaScript 操作样式表能力,部分情况下,为布局树的合成提供基础样式信息;
  • CSS 匹配 HTML 元素很复杂时伴有性能问题。所以,DOM 树要尽量小,CSS 尽量用 id 和 class,请使用少量层级进行嵌套。

构建渲染树

  • 生成 DOM 树和 CSSOM 树以后,就需要将这两棵树组合为渲染树。
  • 创建渲染树:遍历 DOM 树所有可见节点,添加到布局中;忽略不可见节点,如 head 标签中的内容,display:none
  • 如果渲染过程中遇到 JS 文件(script 标签)就停止渲染,JS 文件不只是阻塞 DOM 的构建,也会导致 CSSOM 阻塞 DOM 的构建。所以,如果想让首屏加载得更快,就不应该在首屏加载 JS 文件。

布局与绘制

  • 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)
  • 分层:层叠上下文属性的元素(比如定位属性元素、透明属性元素、CSS 滤镜属性元素(transform))提升为单独的一层,需要裁剪的地方(比如出现滚动条)也会被创建为图层;
  • 图层绘制:完成图层树构建后,渲染引擎会对图层树每一层进行绘制,把一个图层拆分成小的绘制指令,再把指令按照顺序组成一个带绘制列表;
  • 这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小(这一行为也被称为“自动重排”)。
  • 布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。
  • 有些情况图层很大,一次绘制所有图层内容,开销太大,合成线程会将图层划分为图块(256x256 或者 512x512);
  • 合成线程将图块提交给栅格线程进行栅格化,将图块转换为位图。栅格化过程都会使用 GPU 加速,生成的位图保存周期 GPU 内存中;
  • 一旦所有图块都被栅格化,合成线程会生成一个绘制图块命令(DrawQuad),然会将命令提交给浏览器进程,viz 组件接收到该指令,将页面内容绘制到内存中,显示在屏幕上;
  • 布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。

通常情况下 DOM 和 CSSOM 是并行构建的,但是当浏览器遇到 script 标签时(没有 defer 或 async 属性),DOM 将暂停构建(CSSOM 继续构建)。

由于 JavaScript 可以修改 CSSOM,所以需要等 CSSOM 下载并且构建完毕后再执行 JS(确保样式统一),最后才重新构建 DOM。

回流和重绘

回流:Reflow

当通过 JavaScript 或者 CSS 修改了 DOM 尺寸的变化,(宽、高、绘制中移除(display:none))会触发重新布局,浏览器就要重新计算元素的几何属性,将结果绘制出来。回流时其他元素也会受到影响。

重绘:Repaint

重绘:不引起布局变换,直接进入绘制及其以后子阶段;合成:跳过布局和绘制阶段,执行的后续操作,发生在合成线程,非主线程;

导致样式发生变化,却没有影响几何属性(没有修改长宽高,只修改背景颜色 invisible 等,),此时浏览器不需要计算元素的几何属性,直接为改样式绘制新的样式。

回流必定发生重绘,回流所需的成本比重绘高的多,改变前方的节点,可能导致后面所有节点的回流

常见问题

操作 DOM 为什么慢

JS 执行速度相对于浏览器 DOM 操作,要快得多。但是 DOM 是属于渲染引擎的,而 JS 又是 JS 引擎的东西,两个引擎之间的交流,实现起来并不容易。

而每次 JS 对 DOM 进行操作开销会不小,为了减少性能问题,避免短时间内对 DOM 进行大量操作,最佳操作是将操作全部集中,一次性完成渲染。

如何有效减少回流

  • 使用 transform 替代 top
  • 使用 visibility 替换 display:none,因为前者只会引起重绘,后者会引发回流(改变了布局)
  • 不要把节点的属性值放在一个循环里当成循环里的变量。
for(let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
console.log(document.querySelector('.test').style.offsetTop)
}
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  • 避免 CSS 选择器节点层级过多
  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如 <video> 标签,浏览器会自动将该节点变为图层。

数据是如何存储的

栈空间(简单数据类型:number string)和堆空间(复杂数据类型 object)

JavaScript 中的 9 种数据类型,它们可以分为两大类——原始类型和引用类型。

原始类型数据存放在栈中,引用类型数据存放在堆中。堆中的数据是通过引用与变量关系联系起来的。

打开 1 个页面,为什么有 4 个进程

多进程架构:分为浏览器进程、渲染进程、GPU 进程、网络进程、插件进程。

单进程浏览器

1、不稳定。插件、渲染线程崩溃导致整个进程崩溃,从而导致浏览器崩溃。

2、不流畅。脚本(出错,例如死循环)或插件会使浏览器卡顿。

3、不安全。插件和脚本不会被隔离,和系统相关进程共享数据。

多进程浏览器

1、解决不稳定。进程相互隔离,一个页面或者插件崩溃时,影响仅仅时当前插件或者页面,不会影响到其他页面。

2、解决不流畅。脚本阻塞当前页面渲染进程,不会影响到其他页面。

3、解决不安全。采用多进程架构使用沙箱。沙箱看成时操作系统给进程上来一把锁,沙箱的程序可以运行,但是不能在硬盘上写入任何数据,也不能在敏感位置读取任何数据。

线程和进程关系

进程:一个进程是一个程序的运行实例。

  • 进程之间的内容相互隔离。
  • 当一个进程关闭后,操作系统会回收进程所占用的内存。

线程:线程由进程来启动和管理,可以并行处理任务,线程不能单独存在。

  • 线程之间共享进程中的数据。
  • 进程中任意一线程执行出错,都会导致整个进程的崩溃。

缺点:资源占用高、体系架构复杂。