浏览器是怎样工作的

干了一段时间前端,老想着什么时候能搞明白点什么。要搞明白点前端的东西,前端的技术五花八门的,顾不过来,既然顾不过来那就别顾了,找到他们的基石就对了。不管技术怎么变,最终都得在浏览器解析。所以我想着先搞清楚浏览器是咋工作的,可能会让我对前端没那么惊慌。

一google找到了这个姐妹-Tali Garsiel,看到有好几篇文章都是基于她的文章来的。这姐姐人干了N多年的前端开发,看了各个开源浏览器的源码,用尽洪荒之力总结出了这篇文档http://www.taligarsiel.com/Projects/howbrowserswork1.htm。那我也厚颜无耻的用她写的文章作为了解浏览器的入门。虽然文章的年代比较老,但是浏览器的核心概念是不变的,这点从近年来大家的解读可以看出来。

文章主要是针对开源或者部分开源的Chrome、Firefox、Safari。文章有很多章节,我就挑我喜欢的来说了。

The browser’s main functionality

这个章节我了解到一个很有意思的事情就是浏览器的界面,其实是没有任何规范或者说标准的,几大浏览器厂商你抄我我抄你最后大家就基本上长得一样了。比如都有地址栏,都有工具栏等等。

所以浏览器基本上都有以下几大功能:

  • 用来输入 URI 的地址栏
  • 前进和后退按钮
  • 书签设置选项
  • 用于刷新和停止加载当前文档的刷新和停止按钮
  • 用于返回主页的主页按钮

The browser’s high level structure

这章节讲的是浏览器的主要结构。

  • 用户界面 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。

  • 浏览器引擎 在用户界面和呈现引擎之间传送指令。

  • 呈现引擎 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。

  • 网络 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。

  • 用户界面后端 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。

  • JavaScript 解释器。用于解析和执行 JavaScript 代码。

  • 数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

这几个组件中,最主要的或者说前端开发最应该关注的就是呈现引擎了,你想嘛,你做的everything最终不就是为了让浏览器把你想要的效果呈现给用户么,呈现引擎就是干这个事的。

这个地方可能需要注意一下,可能会有疑惑为什么javascript单独拎出来了,请注意啊,javascript它最初的作用就是操作dom,从而实现一些动态效果而发明的。所以它本身是不能直接作为呈现给用户的,需要一个编译执行的过程,所以不属于呈现引擎的组成部分。当前现在javascript不仅用于前端开发了,后端也常常作为某种函数脚本的存在,比如java8以后的MapReduce可运行对应格式的js脚本、java的mango客户端也能用js脚本…

呈现引擎先主要有两种一种是Firefox 的Gecko,这是 Mozilla 公司“自制”的呈现引擎。而 Safari 和 Chrome 浏览器使用的都是 WebKit。不管是哪一种引擎都是为了让浏览器能更好更多的支持内容的呈现。虽然现在浏览器能呈现的内容远远不止HTML+CSS,但是主要的还是对html和css进行解析呈现。

如图所示,呈现引擎一开始通过网络组件请求需要呈现的文档内容,拿到内容后开始解析,首先解析html,把html分割成大量的标记(标签),进而把每个标记转化为一颗dom tree上的节点,同时解析css样式,并和dom tree转换为另一种树:Render Tree(该树包含有多个视觉属性的矩形,并存在排列顺序)。

接着根据Render Tree进行布局,因为呈现树上包含有视觉属性以及顺序,所以可以为呈现树里各个节点(矩形)分配坐标。

分配好坐标以后由另一个组件用户界面后端进行树的绘制。最终呈现给用户。Gecko,WebKit略有不同但是总体的流程是一样的。

整个过程是渐进的过程,浏览器不会等着所有html文档都解析完再呈现,而是解析过程中就开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。

专业解释:HTML 经过解析生成 DOM Tree;而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree(呈现树),最终用来进行绘图。Render Tree 中的元素与 DOM 元素相对应,但非一一对应:一个 DOM 元素可能会对应多个 renderer,如文本折行后,不同的「行」会成为 render tree 种不同的 renderer。也有的 DOM 元素被 Render Tree 完全无视,比如 display:none 的元素。

这儿需要注意的是,html的解析是正向的从上到下(从左到右)的(从根节点开始),而css是逆向的从下到上(从右到左)的。为什么html是从上到下的,这个很好理解,你想想html解析后是一棵树,你想想树的构成肯定是先有个主干然后再有分叉,这样才是一个整体,不然不就是一盘散货么。那css为什么就一定要从下到上呢,通过下面的叙述你会知道如果css也采取从上到下的顺序,那dom tree和style rules两个需要对上眼的消耗是很大的(因为通常dom节点多,css样式也多),从概率上来讲逆向匹配的概率是远远高于正向匹配的概率的。

专业解释:在建立 Render Tree 时, DOM Tree 中的元素需要根据 CSS 的解析结果(Style Rules)来确定生成怎样的 renderer。对于每个 DOM 元素,必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的,在遍历 DOM Tree 时,从 Style Rules 中去寻找对应的 selector。

如果正向解析,例如「div div p em」,我们首先就要检查当前元素到 html 的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第一个 div,回溯若干次才能确定匹配与否,效率很低。

逆向匹配则不同,如果当前的 DOM 元素是 div,而不是 selector 最后的 em,那只要一步就能排除。只有在匹配时,才会不断向上找父节点进行验证。其实更深入的理解需要深入了解Html Parser和css Parser的工作过程。

关键字解释:

style rules

源引: