对于这个过程,我是真的看了好多资料都没能达到非常透彻的理解,以下是我所能了解并理解的一些:特此整理并记录下来,仅供自己的学习啦
关于浏览器的一些基础知识
浏览器的主要功能是向服务器发出请求,在浏览器窗口中显示我们所选择的的网络资源,我们所选择的网络资源一般是指html文档,PDF,图片或其他类型的东西(动态文件之类的),资源的位置由用户使用的URL(统一资源定位符)指定。
浏览器解释并解释html文件的方式是在html和css规范中指定的,这些规范由W3C进行维护,但是这么多年过去了,各大浏览器并没有完全遵从这些规范,同时还在开发自己独有的扩展程序,这对前端开发人员来说就真的太苦逼了,这种兼容性问题实在头疼,如今,大多数浏览器是或多或少的遵从W3C的规范。
用过各种浏览器的肯定也注意到它们彼此之间是有很多相同的元素的,其中包括:
输入栏(URL)
前进和后退
书签设置
刷新按钮和停止加载按钮
返回主界面的按钮
各浏览器也都还有着自己各自的功能,比如Firefox的下载管理器之类的。
浏览器的高层结构
1.用户界面-包括地址栏,前进、后退按钮书签单等,除了浏览器主窗口的显示的页面外,其他显示的各个部分都属于用户界面。
2浏览器引擎-在用户界面和呈现引擎之间传送指令。
3呈现引擎-负责显示请求的内容。如果请求的内容是html,它就解析html和css内容,并将解析后的内容显示在屏幕上。
4.网络-用于网络调用,比如http请求。这个接口与平台无关,并为所有平台提供底层实现。
5.用户界面后端-用于绘制基本的窗口小部件,比如组合框和窗口,其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法
6.Javascript解析器,用于解析和执行javascript代码。
7.数据存储,也就是我们的浏览器的存储,像cookie啦,localstroage啦,sessionstorage(讲到这我又想回顾一下这三个东西的区别了,先说一下,这三个都是存储在浏览器端的,主要是存储的大小不同,数据有效期不同,还是改天再另外撸一篇吧!)h5定义了‘网络数据库’,这是一个完整但是轻便的浏览器内存数据库。
值得注意的是,和大多数浏览器不同,Chrome浏览器的每个标签页都对应一个呈现引擎实例。每个标签页都是一个独立的进程(那么这个就是它完虐各大浏览器的原因之一???,我猜的)
呈现引擎
呈现引擎的作用很明显就是呈现咯,也就是在浏览器的屏幕上显示请求的内容。
默认情况下,呈现引擎可显示html和XML文档与图片。通过插件(浏览器的扩展程序),还可以显示其他类型的内容;使用PDF查看器插件就能显示PDF文档。但是他的主要用途是:显示使用css格式化的html内容和图片。
那么呈现引擎也有好几种,Firefox使用的是Gecko,这是mozilla公司自己开发的呈现引擎,Chrome和safair都是webkit。
webkit是一种开放源代码的呈现引擎,起初用于linux平台,随后由apple公司修改,从而它是支持苹果机和windows的。
接下来看主流程
接下来重点来啦:
呈现引擎将开始解析html文档,并将各标记逐个转化成内容树上的DOM节点。同时会解析外部css文件以及样式元素中的样式数据。html中这些带有视觉指令的样式信息将用于创建另一个树结构,我叫它渲染树,也有人叫它呈现树。
渲染树包含多个视觉属性的矩形,这些矩形的排列顺序就是他们将在屏幕上显示的顺序。
渲染树构建完毕后,进入布局处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标,下一个阶段是绘制--这时呈现引擎会遍历渲染树,由用户界面后端将每个节点绘制出来。
需要着重指出的是,这是一个渐进的过程,为了达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上,它不必等到整个html文档解析完毕,就会开始构建渲染树和设置布局,在不断的接收和处理来自网络的其余内容的同时,呈现引擎将会部分内容解析并呈现出来。
虽然两者的术语不同,但整体流程是基本相同的。
Gecko将视觉格式化元素组成的树称为框架树。每个元素都是一个框架。webkit则是呈现树,它由呈现对象组成,对于元素的位置,webkit使用的术语是布局,而Gecko称之为重排。对于连接DOM节点和可视化信息从而创建呈现树的过程,webkit称为附加,有一个细微的非语义化差别,就是Gecko和html与DOM树之间还有一个称为内容槽的层,用于生成DOM元素。
接下来逐一论述流程中的每一个部分:
解析-综述
解析是呈现引擎中非常重要的一个环节,因此我们要更深入地理解。首先,先来介绍一下解析。
解析文档是指将文档转化成为有意义的结构,也就是可让代码理解和使用的结构。解析得到的结果通常是代表了文档结构的节点数,它称为解析树或者语法树。
讲了这么久的树,那么什么是树,来看:
语法:解析是以文档所遵从的语法规则(编写文档所用的语言或格式)为基础的,所有可以解析的格式都必须对应确定的语法(由词汇和语法规则构成)。这称为与上下文无关的语法。人类语言并不属于这样的语言,因此无法用常规的解析技术进行解析。
(以上这段,我目前都还不是很懂,以后回来看看吧)
解析器和词法分析器的组合
解析的过程可以分为两个子过程:词法分析和语法分析。
词法分析是将输入内容分割成大量标记的过程。标记是语言中的词汇,即构成内容的单位,在人类语言中,它相当于语言字典中的单词。
语法分析是应用语言的语法规则的过程
解析器通常将解析工作分给两个组件来处理:词法分析器(有时候也叫标记生成器),负责将输入内容分解成一个个有效的标记;而解析器负责根据语言的语法规则来分析文档结构,从而构建解析树。词法分析器知道如何将无关的字符(比如空格和换行符)分离出来。
解析是一个迭代的过程。通常,解析器会向词法分析器请求一个新标记,并尝试将其与某条语法规则进行匹配。如果发现了匹配规则,则解析器会将一个对应于标记的节点添加到解析树中,然后继续请求下一个标记。
如果没有规则可匹配,解析器就会将标记存储到内部,并继续请求标记,直至找到可与所有内部存储的标记匹配的规则。如果找不到任何匹配规则,解析器就会引发一个异常。这意味着文档无效,包含语法错误。
翻译:
很多时候,解析树不是最终产品。解析通常是在翻译过程中使用的,而翻译是指将输入文档转换成另一种格式。编译就是这样一个例子。编译器可将源代码编译成机器代码,具体过程是首先将源代码解析成解析树,然后将解析树翻译成机器代码文档。
解析示例:
之前已经看了通过数学表达式简历了解析树。现在,让我们试着定义一个简单的数学语言,用来演示解析的过程。
1:构成语言的语法单位是表达式,项和运算符。
2.我们用的语言可以包含任意数量的表达式。
3.表达式的定义是:一个项接一个运算符,然后再接一个项。
4.运算符是加号或减号。
5.项是一个整数或一个表达式。
好了,可以分析 2+3—1了
匹配语法规则的第一个子串是2,而根据第五条词法规则,这是一个项。匹配语法规则的第二个子串是2+3,而根据第三条规则,这是一个表达式。下一个匹配项已经到了输入的结束。2+3-1是一个表达式,因为我们已经知道2+3是一个项,这样就符合第三条条规则。2++不与任何规则匹配,因此是无效的输入。
好像有点儿懂了
解析器类型
有两种基本类型的解析器:自上而下解析器和自下而上解析器。直观来说,自上而下的解析器从语法的高层结构出发,尝试从中找到匹配的结构。而自下而上的解析器从底层规则出发,将输入内容转化为语法规则,直至满足高层规则。
接下来可以看看这两种解析器会如何解析我们的示例:
自上而下的解析器会从高层开始:首先将2+3标识为一个表达式,然后将2+3-1标识为一个表达式(标识表达式的过程涉及到匹配其他规则,但是起点是最高级别的规则,懂)
自下而上的解析器将扫描输入内容,找到匹配的规则后,将匹配的输入内容替换成规则。如此继续替换,直至输入内容的结尾。部分匹配的表达式保存在解析器的堆栈中。
这种自下而上的解析器称为移位归约解析器,因为输入在向右移位(设想有一个指针从输入内容的开头移动到结尾),并且逐渐归约到语法规则上。
不懂
自动生成解析器
有一些工具是可以帮助生成解析器的,它们称为解析器生成器。我们只要提供我们所用的语法(语法规则和词汇),它就会生成相应的解析器。创建解析器需要对解析器有深刻的理解,而人工创建优化的解析器并不是一件容易的事情,所以解析器生成器是非常实用的。
webkit实用了两种非常有名的解析器生成器:用于创建词法分析器的flex以及用于创建解析器的bison。flex的输入是包含标记的正则表达式定义的文件,bison的输入是采用BNF格式的语言语法规则。
HTML解析器
html解析器的任务是将html标记解析成解析树。
html语法定义
html的词汇和语法在W3C组织创建的规范中进行了定义。当前的版本是html5.
非与上下文无关的语法
正如我们在解析过程的简介中已经了解到,语法可以用BNF等格式进行正式定义。
很遗憾,所有的常规解析器都不适用于HTML(啊,我才知道)。html并不能很容易地用解析器所需的与上下文无关的语法来定义。有一种可以定义html的正规格式:DTD(document type definition),但它不是与上下文无关额语法。
这起初看起来很奇怪:html与XML非常相似。有很多XML解析器可以使用。html存在一个XML变体(XHTML),那么有什么大的区别呢,区别在于html的处理更为“宽容”,它允许你省略某些隐式添加的标记,有时候还能省略一些起始或结束的标记等,和XML严格的语法不同,html整体来看是一种“软性”的语法。
显然,这种看上去细微的差别实际上却带来了巨大的影响。一方面,这是HTML如此流行的原因:它能包容我们的错误;简化网络开发。另一方面,这使得它很难编写正式的语法。概括的说,html无法很容易地通过常规解析器解析(因为它的语法不是与上下文无关的语法),因此也无法通过XML解析器来解析。
HTML DTD
html的定义采用了DTD格式,这个格式可用于定义SGML族的语言。(什么鬼)。它包含了所有允许使用的元素及其属性和层次结构的定义。如上文所述,HTML DTD无法构成与上下文无关的语法。
DTD存在一些变体。严格模式完全遵循html规范,而其他模式可支持以前的浏览器多使用的标记。这样做的目的是确保向下兼容一些旧版本的内容。最新的严格模式DTD在这http://www.w3.org/TR/html4/strict.dtd
DOM
解析器的输出“解析树”是由DOM元素和属性节点构成的树结构。DOM是文档对象模型的缩写,它事HTML文档的对象表示,同时也是外部内容(css js)与html元素之间的接口。
解析算法
HTML无法用常规的自上而下或自下而上的解析器进行解析。原因在于:
1.语言的宽容本质。
2.浏览器历来对一些常见的无效html用法采取包容的态度。
3.解析过程需要不断地反复。源内容在解析过程中通常不会改变,但是在HTML中,脚部标记如果包含document.write,就会添加额外的标记,这样解析过程实际上就更改了输入内容。
由于不能使用常规的解析过程,浏览器就创建了自定义的解析器来解析HTML。http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html,这个地址是html5规范详细德描述了解析算法。此算法由两个阶段组成:标记化和树构建。
标记化是词法分析过程,将输入内容解析成多个标记。HTML标记包括起始标记,结束标记,属性名和属性值。标记生成器识别标记,传递给树构造器,然后接受下一个字符以识别下一个标记;如此反复知道输入结束。
标记化算法
该算法的输出结果是html标记。该算法使用状态机来表示。每一个状态接收来自输入信息流的一个或多个字符,并根据这些字符更新下一个状态。当前标记化状态和树结构状态会影响下一个状态的决定。这意味着,即便接收的字符相同,对于下一个正确的状态也会产生不同的结果,具体取决于当前的状态。该算法相当复杂,无法详述,所以我们通过一个简单的示例来理解。
将下面的html代码标记化:
<html>
<body>
hello xiaoai
</body>
</html>
初始化时数据状态。遇到字符<时,状态更改为“标记打开状态”。接收一个a-z字符会创建“起始标记”,状态更改为“标记名称状态”。这个状态会一直保持到接收>。在此期间接收的每个字符都会附加到新的标记名称上。
遇到>标记时,会发送当前的标记,状态改回“数据状态”。<body>标记也会进行同样的处理。目前html和body标记均已发出。现在我们回到“数据状态”。接收到hello xiaoai 中的h字符时,将创建并发送字符标记,直到接收</body>中的<。我们将为hello xiaoai 中的每一个字符都发送一个字符标记。(不错,讲得好详细,<摊手>)
现在回到“标记打开状态”。接收下一个输入字符/时,会创建 end tag token 并改为“标记名称状态”。我们会再次保持这个状态,直到回收>.然后将发送新的标记,并回到“数据状态”。</html>输入也会同样的处理。
树构建算法
在创建解析器的同时,也会创建document对象。在树构建阶段,以document为根节点的DOM树也会不断进行修改,向其中添加各种元素,标记生成器发送的每个节点都会由树构建器进行处理。规范中定义了每个标记所对应的dom元素。这些元素会在接收到相应的标记时创建。这些元素不仅会添加到dom树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。其算法也可以用状态机来描述。这些状态称为“插入模式”。
还是上面的代码,来看看树构建过程。
<html>
<body>
hello xiaoai
</body>
</html>
树构建阶段的输入是一个来自标记化阶段的标记序列。第一个模式是“initial mode”。接收html标记后转为“before html”模式,并在这个模式下重新处理此标记。这样会创建一个htmlelement元素,并将它附加到document的根对象上。
然后状态改为“before head”。此时我们接收“body”标记。即使我们的示例中没有“head”标记,系统也会隐式创建一个html headelement,并将它附加的树中。
现在进入了“ in head”模式,然后转入“after head”模式。系统对body 标记进行重新处理,创建并插入HTML bodyelement ,同时模式转变为“body”
现在,接收由“hello xiaoai”字符串生成的一系列字符标记。接收第一个字符时会创建并插入“Text”节点,而其他字符也将附加到该节点。
接收body结束标记会触发“after body”模式。现在我们将接收html结束标记,然后进入“after after body”模式。接收到文件结束标记后,解析就此结束。
解析结束后
在此阶段,浏览器会将文档标注为交互状态,并开始解析那些处于“deferred”模式的脚本,也就是那些应在文档解析完成后才执行的脚本。然后,文档状态设置为“完成”,一个“加载”事件将随之触发,链接:http://www.w3.org/TR/html5/syntax.html#html-parser
浏览器的容错机制
我们在浏览html网页时从来不会看到“语法无效”的错误,这是因为浏览器会纠正任何无效内容,然后继续工作。
HTML5 规范了一部分这样的要求,webkit在html解析器类的开头注释对此作了很好的概括:
浏览器对标记化输入内容进行解析,以构建文档树。如果文档的格式正确,就直接进行解析。
遗憾的是,我们不得不处理很多格式错误的html文档,所以解析器必须具备一定的容错性。
我们至少能够处理以下的错误情况:
1明显不能在某些外部标记中添加的元素。此情况下,我们应该关闭所有的标记,直到出现禁止添加的元素,然后再加入该元素。
2.我们不能直接添加的元素。这很可能是网页作者忘记添加了其中的一些标记(或者其中的标记是可选的)。这些标签可能包括:HTML head body tbody tr td li。
3.向inline元素内添加block 元素。关闭所有inline元素,直到出现下一个较高级的block元素。
4.如果这样仍然无效,可关闭所有元素,直到可以添加元素为止,或者忽略该标记。
以上这么多都在讲html的解析,接下来看看css的解析
css 解析
和html不同,css是上下文无关的语法,可以使用各种解析器进行解析。事实上,css规范定义了css的词法和语法。链接:http://www.w3.org/TR/CSS2/grammar.html
webkit css解析器
webkit使用flex和bison解析器生成器,通过css语法文件自动创建解析器。正如我们之前在解析器的介绍中所说,bison会创建自下而上的移位规约解析器。Firefox使用的是人工编写的自上而下的解析器。这两种解析器会将css文件解析成stylesheet对象,且每个对象都包含css规则。css规则包含选择器和声明对象,以及其他与css语法对应的对象。
不太懂
接下来脚本!
处理脚本和样式表的顺序
脚本
网络的模型是同步的,网页作者希望解析器遇到<script>标记时立即解析并执行脚本,文档的解析将停止,直到脚本执行完毕。如果脚本是外部的,那么解析过程会停止,直到从网络同步抓取资源完成后再继续。此模型已经用了很多年了,也在HTML4和html5规范中进行了指定。作者也可以将脚本标注为“defer”,这样它就不会停止解析文档,而是等到解析结束后才执行。h5增加了一个选项,可将脚本标记为异步,以便其他线程解析和执行。
预解析
webkit和Firefox都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源。通过这种方式,资源可以在并行连接上加载,从而提高总体速度,但是,预解析器不会修改DOM树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(js,css,图片)的引用。
样式表
另一方面,样式表有着不同的模型。理论上来说,应用样式表不会更改dom树,因此似乎没有必要等待样式表并停止解析。但这涉及到一个问题,就是脚本在文档解析阶段会请求样式信息,如果当时还没有加载和解析样式,脚本就会获得错误的回复,这样显然会产生很多问题。这看上去是一个非典型案例,但是事实上非常普遍。Firefox在样式表加载和解析的过程中,会禁止所有的脚本。而对于webkit而言,仅当脚本尝试访问样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。
呈现树的构建
在DOM树构建的同时,浏览器还会构建另一个树结构:呈现树。这是可视化元素按照其显示顺序而组成的树,也是文档的可视化表示。它的作用是按照正确顺序绘制内容。
Firefox 将呈现树中的元素称为“框架”。webkit使用的术语是呈现器或呈现对象。
呈现器知道如何布局并将自身及其子元素绘制出来。
webkit的呈现对象类是所有呈现器的基类。
class
RenderObject{
virtual void layout();
virtual void paint(PaintInfo);
virtual void rect repaintRect();
Node* node;
//the DOM node
RenderStyle* style;
// the computed style
RenderLayer* containgLayer;
//the containing z-index layer
}



