因为笔者(爱编程的光头强)近期在写一本关于小程序入门的书籍。其中有一章是介绍虚拟DOM的,它是位于Javascript和真正DOM之间的一层缓存层。为什么引入它,为什么它这么流行,前端三大框架,小程序等,随处可见它的身影。其背后原理是什么。不基于浏览器背后的运行原理,是很难说清楚虚拟DOM被引入的真正原因和最大好处的。
为了弄清楚浏览器背后运行的逻辑,我查了大量资料,不得不吐槽一下,互联网知识尽管多,鱼龙混杂、参差不齐、错漏百出,基本是常态,能找到一篇含金量十分高的文章是不容易的。很幸运,关于这个问题,我还真找到了一篇,本文就是对它的解读。好记性不如烂笔头。
注:本文所有网址都是经过百度短网址处理过的,便于排版及美观。
https://dwz.cn/iv59xbaG 《前端文摘:深入解析浏览器的幕后工作原理》
https://dwz.cn/b72rXXOy 《浏览器的工作原理:新式网络浏览器幕后揭秘》
https://dwz.cn/tn00LUqY 《How browsers work-Behind the scenes of modern web browsers》
https://dwz.cn/bb84qh4a 《HTMLLiving Standard — Last Updated 9 August 2019》
https://drafts.csswg.org/cssom/ 《CSS Object Model (CSSOM)》
https://www.w3.org/TR/css3-conditional《CSS Conditional Rules Module Level 3》
https://fetch.spec.whatwg.org/ 《Fetch Living Standard — Last Updated 9 August 2019》
https://dom.spec.whatwg.org/《DOM Living Standard — Last Updated 9 August 2019》
http://t.cn/AiTQEiZr《Explore the Magic Behind Google Chrome》
https://dwz.cn/LtqlF3Qn 《The Security Architecture of the Chromium Browser》
https://dwz.cn/hfEtd7H7 《Chromium的Render进程启动过程分析》
https://dwz.cn/siobBJ1k 《排版引擎》
本文相当于一篇读书笔记,我们按照原文顺序来一点点深入。 浏览器市场份额占比: Chrome 63.34% Safari 15.06% Firefox 4.48% Samsung Internet 3.77% UC Browser 3.58% Opera 2.58% 从以上浏览器份额,可以看出,chrome占绝对比,所以我们的测试就基于它。
引擎的通俗解释就是驱动器、发动机,在软件中是以一套组件或者扩展程序、包存在的核心代码,相当于计算机的CPU。Wikipedia的解释是浏览器引擎即布局引擎,排版引擎,渲染引擎等。Chrome、Safari使用的是Webkit引擎,Mozilla使用的是Gecho引擎,有时候也叫内核。参考:Https://Dwz.Cn/Tr3rrt8m
以上定义对大众是足够的,但对于想搞清楚浏览器运行机制的人来说,不够。很显然,它笼统的将浏览器几块核心程序(引擎)统称为一个了,并使用其中一个作为全部核心的代称,这很容易让人混淆。
果然,stackoverflow就有人提到了这样的混淆(https://dwz.cn/7ccPJcX6), 本文引用的以色列那位女工程师文章中,将浏览器组件分为了7个,用户界面(UI),浏览器引擎,呈现引擎,网络,用户界面后端,JavaScript 解释器,数据存储等。
这里的浏览器引擎到底指什么呢,很显然它并不是wikipedia里的大众解释,stackoverflow有个答案我觉得比较靠谱,它的解释基于chrome浏览器的多进程架构,笔者(爱编程的光头强)使用chrome自带的中文翻译,翻译了这段,供大家参考:
我不知道如何用“引擎”来解释。让我通过在具有多进程架构的chrome浏览器的上下文中使用的“【
浏览器主进程:管理渲染器进程的主浏览器进程。 渲染器进程:基本上可理解为一个标签卡(chrome)。
可能因为恶意Web内容,导致整个浏览器崩溃或危及宿主机系统,为了防止这种情况发生,浏览器进程委托单独的渲染进程(Renderer进程(选项卡进程))处理每个请求的Web内容,它没有用户权限(即对OS系统调用的访问权限有限)。
当请求网站时,渲染进程将请求转发到浏览器进程,进而发起对导航网站的网络请求。在Web内容到达之后,浏览器进程将内容发送回渲染进程。渲染器进程解析HTML,CSS文件,准备DOM,CSSOM,维护JS运行时(V8实例)并将内容作为位图格式发送到浏览器进程以在UI上显示它。
浏览器进程将渲染器进程视为黑盒子,期望渲染器进程将某种格式的Web内容转换为所需格式,其中包括几个子组件,布局引擎(进程,layout(chrome)/reflow(mozilla)是其中一个。
因此,浏览器进程处理用户特权资源/请求,例如访问文件系统,网络等,其中沙盒渲染器进程负责将网页转换为浏览器进程可以将其显示在OS窗口管理器中的格式。
这其中涉及到两个概念,一个是浏览器进程,一个是渲染进程。
#####回答二:
我认为答案取决于我们在这里讨论的背景。
背景1:如果你正在和一个只知道网络基本知识的朋友交谈......
此上下文中的浏览器引擎是指为您的浏览器提供内容并负责在屏幕上显示内容的软件。如果您在维基百科中搜索浏览器引擎,它会告诉您流行的浏览器引擎包括Webkit,Gecko,Trident等(https://en.wikipedia.org/wiki/Browser_engine)。
在这种情况下,估计很少有人知道有渲染引擎这个东西存在。
背景2:如果你正在和知道浏览器如何工作的朋友以及他们背后的所有疯狂魔法交谈......
此上下文中的浏览器引擎是指浏览器进程,主要负责管理所有I/o进程和显示UI及跟渲染引擎通信等。
浏览器引擎:在UI和渲染引擎之间编组操作。
这也是正确的。如果你看一下Chromium的架构,你会发现浏览器进程/引擎使用渲染进程来协调页面内容。
此上下文中的渲染引擎是指构造DOM,执行JavaScript和布局网页的程序,例如Webkit,Gecko,Trident。渲染引擎由两个主要组件组成:WebCore包含核心布局功能,JavaScriptCore包含JavaScript解释器V8。
您的朋友似乎是专家,还必须了解渲染过程,该过程负责构建网页。渲染引擎只是渲染过程中的关键部分。
下图显示了Chromium体系结构的高级体系结构概述(Google Chrome开源版本)。如果您想了解更多有关现代浏览器背后的魔力的信息,可以查看以下文章:https://medium.com/@zicodeng/explore-the-magic-behind-google-chrome-c3563dbd2739
对文档或其他资源进行解析后,渲染到浏览器窗口显示。它是浏览器的核心部分。通常包含dom解析,css解析,生成render树,layout/reflow,repaint,直至呈现给用户。
文中提到“内容的大小一般限制在 8000 个chunck块以内”,解释:Web服务器生成HTTP Response时无法在Header就确定消息大小的,这时一般来说服务器将不会提供Content-Length的头信息,而采用Chunked编码动态的提供body内容的长度。进行Chunked编码传输的HTTP Response会在消息头部设置: Transfer-Encoding: chunked
浏览器请求到HTML代码后,在生成DOM的开始阶段,并行发起css、图片、js的请求。CSS文件下载完成后开始构建CSSOM。CSSOM构建完成后和DOM合并生成Render Tree(attachment)。浏览器计算出每个节点在屏幕的位置进行布局。布局完成后,通过显卡,将内容画到屏幕上。JS修改了DOM或者CSS属性后,Layout和Paint也会被重复执行。图片下载完成后也需要调用Layout和Paint来更新网页。
从dom,cssom到渲染树(frame树)的过程不同的内核树的概念不太一样的,不过所做的工作都大同小异,就是计算形成若干能用于布局的矩形盒子(宽度、高度和位置等几何信息,该计算过程具体实现在layout或reflow阶段),box模型。
Gecko 将视觉格式化元素组成的树称为“框架树”。每个元素都是一个框架。WebKit 使用的术语是“呈现树”,它由“呈现对象”组成。对于元素的放置,WebKit 使用的术语是“布局”,而 Gecko 称之为“重排”。对于连接 DOM 节点和可视化信息从而创建呈现树的过程,WebKit 使用的术语是“附加”。有一个细微的非语义差别,就是 Gecko 在 HTML 与 DOM 树之间还有一个称为“内容槽”的层,用于生成 DOM 元素。
树包含 DOM 节点,指的树是由实现了某些DOM 接口的元素构成的。每个元素都有一条原型链,标准定义了每个DOM节点或元素必须要实现的DOM接口(属性,方法,事件)。
通常html被解析的时候遇到js会阻塞执行,为了优化体验和速度,采用延迟或异步的方式,此时就存在了资源的预解析或异步执行的过程了。
共享样式数据 WebKit 节点会引用样式对象 (RenderStyle)。这些对象在某些情况下可以由不同节点共享。这些节点是同级关系,并且:
这些元素必须处于相同的鼠标状态(例如,不允许其中一个是“:hover”状态,而另一个不是) 任何元素都没有 ID 标记名称应匹配 类属性应匹配 映射属性的集合必须是完全相同的 链接状态必须匹配 焦点状态必须匹配 任何元素都不应受属性选择器的影响,这里所说的“影响”是指在选择器中的任何位置有任何使用了属性选择器的选择器匹配 元素中不能有任何 inline 样式属性 不能使用任何同级选择器。WebCore 在遇到任何同级选择器时,只会引发一个全局开关,并停用整个文档的样式共享(如果存在)。这包括 + 选择器以及 :first-child 和 :last-child 等选择器。
代码:
<div>
<ul>
<li>11</li>
<li>22</li>
<li>33</li>
</ul>
</div>
divulli{color:red;width:100px;height:36px;}
以上代码就属于符合要求的公共样式。
为避免对所有细小更改都进行整体布局,浏览器采用了一种“dirty 位”系统。如果某个呈现器发生了更改,或者将自身及其子代标注为“dirty”,则需要进行布局。
有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代需要布局。
在浏览器层面已经考虑到了因为某些细小改动而发生全局重新计算和布局的情况。所以虚拟DOM的出现只是为了避免很多人为的行为导致的不必要重排,从而提升性能。
如果布局是由“大小调整”或呈现器的位置(而非大小)改变而触发的,那么可以从缓存中获取呈现器的大小,而无需重新计算。 在某些情况下,只有一个子树进行了修改,因此无需从根节点开始布局。这适用于在本地进行更改而不影响周围元素的情况,例如在文本字段中插入文本(否则每次键盘输入都将触发从根节点开始的布局)。
Tali Garsiel and Paul Irish的这篇文章很长,也很有深度,适合反复阅读,几次是很难完全理解的,而且涉及的知识面非常之广,要彻底弄明白,至少也成了半个浏览器专家和排版专家了。不过这是有意义的。本文是针对其中部分疑点做了扩展阅读,还有很多不明白的地方,之后抽时间深入涉猎。