一、浏览器的主要组件
浏览器的主要组件有以下几种,各大浏览器可能存在细微不同:
-
界面控件(User Interface):包括地址栏、前进后退按钮、书签菜单等除了网页显示区域以外的部分;
-
浏览器引擎(Browser engine):浏览器引擎充当用户界面和渲染引擎之间的中介或桥梁。它根据从用户界面接收到的输入来查询和处理渲染引擎;
-
渲染引擎(Rendering engine):负责显示请求的内容。例如,如果请求的内容是 HTML,则渲染引擎会解析 HTML 和 CSS,并将解析后的内容显示在屏幕上;
注意:Chrome等浏览器会运行多个渲染实例:每个选项卡一个。每个选项卡运行在单独的进程中。
注意:每个浏览器都有自己独特的渲染引擎。不同浏览器版本的渲染引擎也可能不同。下面的列表提到了一些常见浏览器使用的浏览器引擎:
- Google Chrome and Opera v15+: Blink
- Internet Explorer: Trident
- Mozilla Firefox: Gecko
- Chrome for iOS and Safari: WebKit
-
网络(Networking):用于网络请求(如 HTTP 或 FTP)。它包括平台无关的接口和各平台独立的实现;
-
UI后端(UI Backend):绘制基础元件,如组合框与窗口。它提供平台无关的接口,内部使用操作系统的相应实现;
-
JS解释器(JavaScript Interpreter):用于解析和执行JavaScript代码,例如 V8 引擎;
-
数据存储持久层(Data Persistence):浏览器需要把所有数据存到硬盘上,如cookies。浏览器还支持 localStorage、IndexedDB、WebSQL 和 FileSystem 等存储机制。
二、浏览器中的进程和线程
2.1 进程和线程
进程:进程是操作系统资源分配的基本单位,进程中包含线程。简而言之,就是正在运行的程序。
线程:线程是由进程所管理的。是进程内的一个独立执行的单位,是CPU调度的最小单位。
线程和进程的区别:
- 线程是CPU调度的最小单位,而进程是操作系统分配资源的最小单位;
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
线程和进程的区别是什么?- 知乎 https://www.zhihu.com/question/25532384/answer/411179772
2.2 浏览器中的五种进程
Chrome中 shift + esc 可以查看Chrome的任务管理器
-
浏览器进程:负责控制浏览器除标签页外的界面,包括地址栏、书签、前进后退按钮等,以及负责与其他进程的协调工作,同时提供存储功能;
-
GPU进程:负责整个浏览器界面的渲染。Chrome刚开始发布的时候是没有GPU进程的,而使用GPU的初衷是为了实现3D CSS效果,只是后面网页、Chrome的UI界面都用GPU来绘制,这使GPU成为浏览器普遍的需求,最后Chrome在多进程架构上也引入了GPU进程;
-
网络进程:负责页面网络的资源加载,发起和接受网络请求,以前是作为模块运行在浏览器进程里面的,后面才独立出来,成为一个单独的进程;
-
插件进程:负责插件的运行,因为插件可能崩溃,所以需要通过插件进程来隔离,以保证插件崩溃也不会对浏览器和页面造成影响。出于安全考虑,插件进程都是运行在沙箱模式下的;
每个扩展程序都是一个插件进程,如果没有启用任意一个扩展程序,那么插件进程的个数就是0。
所以我们开启一个页面,至少会启动4个进程,分别是:1个浏览器进程、1个GPU进程、1个网络进程和1个渲染进程。
如果后续再打开新的标签页,浏览器进程、GPU进程和网络进程都是共享的,不会再启动。
-
渲染进程:核心任务是将
HTML
、CSS
和JavaScript
转换为用户可以与之交互的网页,排版引擎Blink
和 JS解析器V8引擎
都运行在该进程中,默认情况下,Chrome为每一个Tab标签页创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下的。默认情况下会为每一个标签页配置一个渲染进程,但是也有例外,比如从A页面里面打开一个新的页面B页面,而A页面和B页面又属于同一站点的话,A和B就共用一个渲染进程,其他情况就为B创建一个新的渲染进程。
我们平时看到的浏览器呈现出页面过程中,大部分工作都是在渲染进程中完成,所以我们来看一下渲染进程中的线程。
2.3 渲染进程中的线程
- GUI渲染线程:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行;
- JS引擎线程:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS,处理页面中用户的交互,以及操作DOM树、CSS样式树。它GUI渲染线程不能同时执行,只能一个一个来,如果JS执行过长就会导致阻塞掉帧,GUI渲染线程与JS引擎线程互斥;
- 计时器线程:指 setInterval 和 setTimeout,因为JS引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来负责计时器工作;
- 异步HTTP请求线程:XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入事件队列,等待JS引擎空闲执行;
- 事件触发线程:主要用来控制事件循环,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。
三、浏览器渲染流程
在浏览器地址栏输入网址并敲下回车的那一刻会发生什么?
- 对浏览器输入的地址进行 DNS 解析,将域名解析成对应的 IP 地址,如果输入的直接就是 IP 地址,则免去此步骤;
- 建立 TCP 连接,三次握手;
- 向这个 IP 地址发送 http 请求,服务器收到这个 http 请求进行处理和响应;
- 最终浏览器得到服务器响应的内容(一般是一个 html 文件),并将该内容交给渲染引擎处理;
- 数据传送完毕,断开 TCP 连接,四次挥手。
当浏览器获得一个 html 文件时,会 自上而下 的加载,并在加载过程中进行解析渲染。
- 生成DOM树:获取 HTML 文件并进行解析,生成 DOM (Document Object Model/文档对象模型)树;
- 执行脚本:遇到 script 标签时,DOM 树构建将暂停,直至脚本加载并执行完毕;
- 生成CSSOM树:解析 HTML 的同时也会解析 CSS,生成 CSSOM (CSS Object Model)树;
- 构建渲染树:将 DOM 树和 CSSOM 树结合,生成渲染树(Render Tree);
- 布局渲染树:根据生成的渲染树,生成布局 (Layout) (这是一次回流),得到节点的几何信息(位置,大小等);
- 绘制渲染树:根据渲染树以及回流得到的几何信息,进行绘制 (Painting) (这是一次重绘),绘制完毕后进行 Display 展示。
script 标签的 async 和 defer 属性(仅对外链有效,也就是带src属性的script标签):
- 两者都没有:立即加载并执行脚本,暂停渲染DOM,直至脚本执行完毕。
- 有 async:加载和渲染后续文档元素的过程将和 script 的加载与执行并行进行(异步)
- 有 defer:加载后续文档元素的过程将和 script 的加载并行进行(异步),但是 script 的执行要在所有元素解析完成之后
四、V8 引擎
![V8引擎](https://i-blog.csdnimg.cn/blog_migrate/3a25fa9f229143f5855a5fc055a38ad3.png)
V8 是 Google 的开源高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写。它用于 Chrome 和 Node.js 等。它实现了 ECMAScript 和 WebAssembly,并运行在 Windows 7 或更高版本、macOS 10.12+ 和使用 x64、IA-32、ARM 或 MIPS 处理器的 Linux 系统上。V8 可以独立运行,也可以嵌入到任何 C++ 应用程序中。
计算机无法识别 JS 代码,需要转换成机器码才能被计算机识别。JS 引擎的作用就是帮助我们将 JS 代码翻译成机器码供 CPU 去执行。
V8 引擎的主要组成部分:
- Parse 模块:负责将源代码解析成 AST 树(抽象语法树);
- Ignition 解释器:负责将 AST 树转换成字节码,同时会标记热点代码(HotCode);
- TurboFan 编译器:负责将热点代码编译成优化后的机器码;
- Orinoco 垃圾回收器:负责回收不再需要的内存空间。
4.1 Parse 模块
Parse 模块负责将源代码解析成 AST 树,其中有三个重要阶段:词法分析(Scanner)、语法分析(Parser) 和 预解析(PreParser)。
- Scanner 扫描器:Scanner 会进行词法分析,将源代码逐词转换成tokens;
- Parser 解析器:Parser 会进行语法分析,将 tokens 转换成AST树;
- PreParser 预解析器:PreParser 会将不是立即执行的函数进行预解析,会验证该函数的语法是否正确,解析函数声明以及确定函数作用域。当函数被调用时会将其交给 Parser 进行完全解析。
4.2 Ignition 解释器
![](https://i-blog.csdnimg.cn/blog_migrate/3e486d2264c0d1ba08bdd5a910dd3a5e.png)
Ignition 解释器会将 AST 树编译成字节码(bytecode),开始逐句对字节码解释成机器码并执行。在解释执行的过程中,会标记重复执行的代码为热点代码,将热点代码交给TurboFan处理。
字节码是机器码的抽象,可以看作是小型的构建块。相比机器码,字节码不仅占用内存少,而且生成字节码的时间很快,提升了启动速度。
4.3 TurboFan 编译器
![](https://i-blog.csdnimg.cn/blog_migrate/719f9500b4a216672bcfd3c07e9ced75.png)
TurboFan 编译器负责将热点代码编译为更高效的机器码储存起来,等到下次执行这段代码的时候,就会用这个机器码直接执行,提升了代码的执行效率。
4.4 Orinoco 垃圾回收器
V8 引擎的垃圾回收器经历过很多变化,当前版本的 GC 叫做 Orinoco。垃圾回收器就负责将不再需要的内存空间释放掉。
在浏览器的发展历史上对于垃圾回收主要有两种 GC 的算法:
-
引用计数:当一个对象有一个引用指向它时,那么这个对象的引用就+1,当一个对象的引用为0时,这个对象就可以被销毁掉。引用计数存在一个很大的弊端就是会出现循环引用导致无法消除。目前逐渐被 弃用 了。
-
标记清除:这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有被引用到的对象,对于那些没有引用到的对象,就认为是不可用的对象,随后便会销毁掉。目前JS引擎采用较广泛的就是标记清除法。当然 V8 引擎也对该算法进行了进一步的优化。