浏览器工作原理(五) CSS解析(CSS parsing)
还记得简介中提到的解析的概念吗,不同于html,css属于上下文无关文法,可以用前面所描述的解析器来解析。Css规范定义了css的词法及语法文法。
看一些例子:
每个符号都由正则表达式定义了词法文法(词汇表):
comment///*[^*]*/*+([^/*][^*]*/*+)*//num[0-9]+|[0-9]*"."[0-9]+nonascii[/200-/377]nmstart[_a-z]|{nonascii}|{escape}nmchar[_a-z0-9-]|{nonascii}|{escape}name{nmchar}+ident{nmstart}{nmchar}*
“ident”是识别器的缩写,相当于一个class名,“name”是一个元素id(用“#”引用)。
语法用BNF进行描述:
ruleset: selector [ ',' S* selector ]*'{' S* declaration [ ';' S* declaration ]* '}' S*;selector: simple_selector [ combinator selector | S+ [ combinator selector ] ];simple_selector: element_name [ HASH | class | attrib | pseudo ]*| [ HASH | class | attrib | pseudo ]+;class: '.' IDENT;element_name: IDENT | '*';attrib: '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*[ IDENT | STRING ] S* ] ']';pseudo: ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ];说明:一个规则集合有这样的结构div.error , a.error {color:red;font-weight:bold;}div.error和a.error时选择器,大括号中的内容包含了这条规则集合中的规则,这个结构在下面的定义中正式的定义了:ruleset: selector [ ',' S* selector ]*'{' S* declaration [ ';' S* declaration ]* '}' S*;
这说明,一个规则集合具有一个或是可选个数的多个选择器,这些选择器以逗号和空格(S表示空格)进行分隔。每个规则集合包含大括号及大括号中的一条或多条以分号隔开的声明。声明和选择器在后面进行定义。
Webkit CSS解析器(Webkit CSS parser)
Webkit使用Flex和Bison解析生成器从CSS语法文件中自动生成解析器。回忆一下解析器的介绍,Bison创建一个自底向上的解析器,Firefox使用自顶向下解析器。它们都是将每个css文件解析为样式表对象,每个对象包含css规则,css规则对象包含选择器和声明对象,以及其他一些符合css语法的对象。图12:解析css
处理脚本及样式表的顺序
脚本
web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步asyn的选项,以使脚本的解析执行使用另一个线程。
预解析
Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。样式表(Style sheets)
样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而Chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。------------------------------------下一节,渲染树构建------------------------------------