前言
最近想要把旧的知识点整理起来,为面试做准备,于是打算从头开始,把css和html重新复习一遍,记一些css的面试题。不过,这个过程挺无聊的,虽然css能做很漂亮的界面,但是css要记的知识点太多了,而且很多也没有什么逻辑性可言,最关键的是我css和html很多都忘了…因为有很多UI库可以使用。当然了肯定是框架工具要会用,原理也要懂,这样才能又能造轮子,又能写外包…,所以,我就又开了篇博客,用来记录下webkit的探索之旅。先说明下,我这篇文章中的观点,也是从网上的博客中东拼西凑,再加入些自己的观点和理解写的,毕竟我也没去看webkit 的源码,所以可能在文章会出现偏差,也请读者自行鉴别。
webkit 介绍
先来介绍下webkit 是个啥吧,webkit最早是苹果的Safari 浏览器的内核,后来的chrome浏览器也采用了webkit。浏览器的内核简单来说就是渲染引擎,其作用就是把html,css,js 这些字符串渲染成一张图像,显示出来。具体划分的话,内核分为排版引擎和js引擎,排版引擎用于界面的绘制和渲染,js引擎用于执行js代码。浏览器的内核也有很多种,常见的如下。
| 内核名称 | 作用 |
|---|---|
| Trident | 大名鼎鼎的IE浏览器的内核,IE8的JavaScript引擎是Jscript,IE9开始用Chakra |
| Gecko | 火狐浏览器的内核,JavaScript引擎是:SpiderMonkey(1.0-3.0)/ TraceMonkey(3.5-3.6)/ JaegerMonkey(4.0) |
| Webkit | Safari和Chrome的内核,包含WebCore排版引擎及JavaScriptCore解析引擎,在chrome浏览器中,把js引擎改换成了v8. |
| Blink | 算是webkit 内核的衍生物,google在webkit的基础上开发出的浏览器内核,还是使用WebCore和v8。现在Chromium和chrome 都是使用了这个浏览器内核。 |
这里,还有一个问题就是 ios 和 安卓 因为 苹果 和 google 的 原因,所以 也都是使用了webkit的内核,(当然安卓现在准确的讲使用的是基于Blink的Chromium)。所以 可以说随着 ie 使用人数的减少 ,webkit 内核已经占领了 桌面 和 移动端 的绝大多数。
还有一个有意思的点,webkit 之所以开源,是因为 它的 前身是 KDE的一个开源项目 KHTML(html引擎),苹果在khtml的基础上开发出了WebCore,同时在kjs(ktml配套的js引擎)的基础上开发了JavaScriptCore,并开源出来。然后google 又在 webkit 的基础上保留了WebCore,加入了v8 ,开发出了Blink引擎,所以Blink和webkit其实都可以看成是 KHTML 的衍生物,在保留了KHTML 的基础上进行了修改,所以,本文也不做具体区分了,把 chrome 的内核也称为 webkit。
浏览器的架构
浏览器的组成
在上面有了个对webkit的简单理解之后,再来说下浏览器的架构,因为浏览器内核是在浏览器中被使用到的,如果在安卓和ios上,则是使用了webview组件去调用浏览器内核。因为浏览器有很多种,这里,就以开发中最常使用到chrome浏览器为例。
首先,浏览器大致可以分为5个部分,分别是
- 用户界面(地址栏、前进/后退按钮、书签菜单等)
- 浏览器引擎(在用户界面和渲染引擎之间传送指令)
- 渲染引擎(解析 HTML、CSS和JS并呈现页面)
- 后端服务层(网络、数据存储如Cookie、Storage等)
- 特别服务层(记住密码、暗黑模式等)
结构图如下:

多进程架构
chrome 在处理这些浏览器结构的时候,使用了多进程进行处理。当使用了chrome浏览器打开网页的时候,打开系统的任务管理器,就可以看到chrome是有多个进程存在的。
chrome 的多进程架构图如下

这其中最重要的就是渲染进程和主进程了,主进程的主要负责功能是显示浏览器的界面,子进程的管理和提供存储功能等,而渲染进程就是浏览器的核心了,Chrome中只有一个主进程,但是可以存在多个渲染进程。渲染进程核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
还有一个有意思的框架可以学下,就是electron ,它就是基于这种多进程的浏览器架构和node.js实现的一个用于开发桌面应用程序的框架,它的一个窗口就是一个渲染进程,主进程用于创建渲染进程并控制渲染进程,和渲染进程之间进行必要的通信IPC 。不过和chrome有区别的是,渲染进程除了能够使用DOM的api外,还能使用node.js的api。我觉得学习electron,对于理解chrome浏览器的多进程架构是很有帮助的。还有vscode就是基于electron开发的。
这里,盗张慕课网的图,嘻嘻

再来说下,这种多进程架构的优缺点吧,通过给每一个tab窗口开一个进程,这样最大的好处,就是进程的内存空间是独立的,页面之间是不会相互影响的,所以就算浏览器中的一个页面出现了崩溃的问题,对于其它打开的浏览器页面并不会造成影响,而且,因为内存空间相互隔离的原因,所以不同的tab页面之间是不能访问(除非使用IPC的方式),所以保证了安全性。当然,这样做的方式也是有缺点的,那就是多进程会加大内存的开销,这点在window 系统上会体现得更加得明显,虽然,chrome 也进行了很多的优化。
因为这篇文章的主体是要介绍webkit,所以这里还是简单的引出就好,更多详细的Chromium架构模型可以看我的第二篇文章。
WebKit 架构
简单扯完了浏览器的架构,再回来说webkit。因为浏览器的渲染进程会调用到浏览器的渲染引擎来完成页面的渲染,所以 chrome 的 渲染进程 会 利用webkit 来 实现 html,css 的解析 和 js 代码的执行。
下面是webkit的架构图

实线部分是共享的,虚线部分会根据不同的平台有不同的实现。
比如js 引擎方面 ,Chrome 就使用了 v8。
还有就是webkit2 已经支持了多进程的使用了,即分UI进程和Web进程,Web进程用于页面渲染,UI进程用于页面的展示。但因为在chrome中, chrome有自己的一套多进程架构,webkit 还是一个库的形式被渲染进程调用,还是以多线程的方式负责页面的渲染工作,所以这里简单提一下就可以了。
WebCore的架构图

webkit的工作流程
知道了webkit的大致架构和作用后,接下来,就是了解webkit的工作流程了,先上张webkit的主要工作流程图

接下来,按照这张图,我来简单说下,webkit的工作流程,webkit再拿到html.css.js这些资源文件之后,就会开始解析,把html解析成dom树,把css解析成 style rules, 然后生成结合两者生成 render 树,再生成 Graphics Layer tree ,然后再进行光栅化产生纹理,最后合成纹理,展示在屏幕上。
生成dom树
在chrome 浏览器中,打开一个新的标签页,渲染进程就会创建一个新的webview,该webview是整个网页生成和渲染的入口,webview的构造函数会创建一个新的page对象,它和webview相对应,每个webview只存在一个page对象,每个page中有且仅有一个mainFrame,每个Frame 都有自己的一个 Document,在MainFrame中可以生成多个子Frame,就是我们平常见到的网页中的多Frame页面。Document 对应到的也就是DOM中的document对象,不过。此时,它是在webcore 中,而js 能 访问到的对象 是 在 js 引擎中,所以,这里涉及到对象注入的问题,后面再讲。
在把页面的基础对象建立后,拿到了html文件,就是用html-parser 模块 开始 html 的解析,解析的过程是从上到下,如果在html页面中出现了script标签 ,那么默认是需要等待script 标签中的 js 代码 解析执行完毕后,再往下解析 html的,而如果script 标签使用了外链的方式,那么还需要等待网络去获取 js 脚本,正因为js 代码的执行会阻塞 主线程 解析 html ,所以一般推荐 是 把 script 标签都放在 页面的 最后,还有就是虽然js线程和渲染线程是互斥的,但它们是运行在不同的线程上。webkit为了提供性能,所以针对这个问题,做了优化,就是html文档的预解析,当遇到js脚本时,主线程会停止html的解析,但是会尝试去加载其它的图片,音视频资源文件等。当html解析完毕后,dom树也就构建完毕了。每个html标签都会被解析成HTMLelement,解析完成后,就可以在js线程访问到了。
生成css树
css的文件的加载是异步的,css的解析和html的解析是并发进行的,其实render树的生成和dom 树的生成也是同时进行的,每解析一个html标签,就会挂载一个dom结点,同时,dom结点也会去和css tree 结合,生成 renderlayer Tree ,每个 renderlayer 结点 就是 一个css 的 盒子,当dom树构建完毕,render 树也就构建完毕了 。因为可能会出现js 代码中 访问 css 的情况,所以 webkit 会暂停 那些 试图 访问 某些没有被加载完成的 css 属性 的 js 代码。
重绘和回流
回流也叫做重排,当节点的尺寸和位置发生改变的时候,就会触发回流,回流会计算页面的布局,并把受影响的部分重新绘制到屏幕。当页面第一次加载的时候,因为要构建布局,所以必然会触发回流。
当渲染树中的一些元素需要更新一些不会改变元素不局的属性,比如只是影响元素的外观、风格、而不会影响布局的那些属性,这时候就只发生重绘。当然,页面首次加载也是要重绘一次的。
光栅化
renderlayer Tree 会进一步构建成 Graphics Layer tree ,也就是图层, 然后进行光栅化,所谓光栅化就是要对 Graphics Layer 进行实际的绘制,这里会使用到底层的一些 图形库完成绘制,而且根据方式的不同,也分为使用CPU(软件渲染)和使用GPU(硬件渲染)两种,如果使用的是CPU 进行光栅化,那么在 chrome 使用的就是Skia 库,如果使用的是 GPU 进行光栅化,那么使用的就是opengl,光栅化后的 图像叫纹理,不过使用 CPU光栅化有个缺点,就是 cpu 光栅化后的纹理仍然需要上传到 gpu,纹理其实就是由一个个像素点组成的图像,只不过还没有显示到屏幕上,最后使用 gpu 进行纹理的合成,就可以显示在 屏幕上了。
Dom binding
再回到前面的问题,如果需要把webkit中实现的dom对象或html5对象暴露给javascript,让web开发者在javascript 中能够访问到,就需要在webkit 中为每个对象实现相应的js binding文件,以v8的引擎为例,需要为HTMLDocument 对象实现一个 V8HTMLDocument 的 对象,目的是转化 HTMLDocument 对象 为 v8:Object 。但是 DOM 的对象是很庞大的,再加上后续的html5 对象,如果给每一个cpp文件实现一个相应的转换类,工作量会很大,所以为了解决这个问题,webkit 编写了一套工具,去自动生成 binding 文件,这个过程需要利用到WebIDL ,它是一种特殊的定义语言,用来定义webcore的接口如何绑定到外部语言,这样,只要把webIDL 文件解析执行了,生成的cpp代码就会绑定到webcore的接口,然后再用v8去调用生成的代码,就可以实现v8访问webcore了。
总结
webkit 实际工作的流程比我上面介绍的要复杂的多,很多流程我都是简述的,但是可以简单一点的说,就是在chrome 中,webkit的核心是webcore,它的工作就是把html和css渲染成图层,同时要将CSSOM 和 DOM 注入到 js 引擎中 ,当 js 引擎修改了 DOM 的结构时,重新 渲染图层 。大体就先介绍到这了。写这篇文章也是坑坑洼洼,因为webkit是cpp写的,而且chrome本身也是涉及到比较多的系统知识,例如icp,所以,去看很多前端类文章并没什么价值,chrome架构的具体介绍,我会在下一篇文章中讲,未完待续…
参考文章
https://ming1016.github.io/2017/10/11/deeply-analyse-webkit/