css识别变量中的换⾏符_认真介绍CSS原理
作为前端,我们每天都在与CSS打交道,那么CSS的原理是什么呢?
⼀、浏览器渲染
开篇,我们还是不厌其烦的回顾⼀下浏览器的渲染过程,先上图:
正如上图所展⽰的,我们浏览器渲染过程分为了两条主线:
其⼀,HTML Parser ⽣成的 DOM 树;
其⼆,CSS Parser ⽣成的 Style Rules ;
在这之后,DOM 树与 Style Rules 会⽣成⼀个新的对象,也就是我们常说的 Render Tree 渲染树,结合 Layout 绘制在屏幕上,从⽽展现出来。
本⽂的重点也就集中在第⼆条分⽀上,我们来探究⼀下 CSS 解析原理。
⼆、Webkit CSS 解析器
浏览器 CSS 模块负责 CSS 脚本解析,并为每个 Element 计算出样式。CSS 模块虽⼩,但是计算量⼤,设计不好往往成为浏览器性能的瓶颈。
CSS 模块在实现上有⼏个特点:CSS 对象众多(颗粒⼩⽽多),计算频繁(为每个 Element 计算样式)。这些特性决定了 webkit 在实现 CSS 引擎上采取的设计,算法。如何⾼效的计算样式是浏览器内核的重点也是难点。
先来看⼀张图:
Webkit 使⽤ Flex 和 Bison 解析⽣成器从 CSS 语法⽂件中⾃动⽣成解析器。
它们都是将每个 CSS ⽂件解析为样式表对象,每个对象包含 CSS 规则,CSS 规则对象包含选择器和声明对象,以及其他⼀些符合 CSS 语法的对象,下图可能会⽐较明了:
Webkit 使⽤了⾃动代码⽣成⼯具⽣成了相应的代码,也就是说词法分析和语法分析这部分代码是⾃动⽣成的,⽽ Webkit 中实现的CallBack 函数就是在 CSSParser 中。
CSS 的⼀些解析功能的⼊⼝也在此处,它们会调⽤ lex , parse 等⽣成代码。相对的,⽣成代码中需要的 CallBack 也需要在这⾥实现。
举例来说,现在我们来看其中⼀个回调函数的实现,createStyleRule(),该函数将在⼀般性的规则需要被建⽴的时候调⽤,代码如下:
CSSRule* CSSParser::createStyleRule(CSSSelector* selector)
{
CSSStyleRule* rule = 0;
if (selector) {
rule = new CSSStyleRule(styleElement);
m_parsedStyleObjects.append(rule);
rule->setSelector(sinkFloatingSelector(selector));
rule->setDeclaration(new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedPropertie
s));
}
clearProperties();
return rule;
}
从该函数的实现可以很清楚的看到,解析器达到某条件需要创建⼀个 CSSStyleRule 的时候将调⽤该函数,该函数的功能是创建⼀个CSSStyleRule ,并将其添加已解析的样式对象列表 m_parsedStyleObjects 中去,这⾥的对象就是指的 Rule 。
那么如此⼀来,经过这样⼀番解析后,作为输⼊的样式表中的所有 Style Rule 将被转化为 Webkit 的内部模型对象 CSSStyleRule 对象,存储在 m_parsedStyleObjects 中,它是⼀个 Vector。
但是我们解析所要的结果是什么?
1、通过调⽤ CSSStyleSheet 的 parseString 函数,将上述 CSS 解析过程启动,解析完⼀遍后,把 Rule 都存储在对应的CSSStyleSheet 对象中;
2、由于⽬前规则依然是不易于处理的,还需要将之转换成 CSSRuleSet。也就是将所有的纯样式规则存储在对应的集合当中,这种集合的抽象就是 CSSRuleSet;
3、CSSRuleSet 提供了⼀个 addRulesFromSheet ⽅法,能将 CSSStyleSheet 中的 rule 转换为 CSSRuleSet 中的 rule ;
4、基于这些个 CSSRuleSet 来决定每个页⾯中的元素的样式;
三、CSS 选择器解析顺序
可能很多同学都知道排版引擎解析 CSS 选择器时是从右往左解析,这是为什么呢?
1、HTML 经过解析⽣成 DOM Tree(这个我们⽐较熟悉);⽽在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容⼀起进⾏分析建⽴⼀棵 Render Tree,最终⽤来进⾏绘图。Render Tree 中的元素(WebKit 中称为「renderers」,Firefox 下为「frames」)与 DOM 元素相对应,但⾮⼀⼀对应:⼀个 DOM 元素可能会对应多个 renderer,如⽂本折⾏后,不同的「⾏」会成为 render tree 种不同的renderer。也有的 DOM 元素被 Render Tree 完全⽆视,⽐如 display:none 的元素。
2、在建⽴ Render Tree 时(WebKit 中的「Attachment」过程),浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果(Style Rules)来确定⽣成怎样的 renderer。对于每个 DOM 元素,必须在所有
Style Rules 中到符合的 selector 并将对应的规则进⾏合并。选择器的「解析」实际是在这⾥执⾏的,在遍历 DOM Tree 时,从 Style Rules 中去寻对应的 selector。
3、因为所有样式规则可能数量很⼤,⽽且绝⼤多数不会匹配到当前的 DOM 元素(因为数量很⼤所以⼀般会建⽴规则索引树),所以有⼀个快速的⽅法来判断「这个 selector 不匹配当前元素」就是极其重要的。
4、如果正向解析,例如「div div p em」,我们⾸先就要检查当前元素到 html 的整条路径,到最上层的 div,再往下,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第⼀个 div,回溯若⼲次才能确定匹配与否,效率很低。
对于上述描述,我们先有个⼤概的认知。接下来我们来看这样⼀个例⼦,参考地址:
<div>
<div class="jartto">
<p>span> 111 span><p>
<p>span> 222 span><p>
<p><span> 333 <span><p>
<p><span class='yellow'> 444 <span><p>
<div>
<div>
CSS 选择器:
div > div.jartto llow{
color:yellow;
}
对于上述例⼦,如果按从左到右的⽅式进⾏查:
1、先到所有 div 节点;
2、在 div 节点内到所有的⼦ div ,并且是 class = “jartto”;
3、然后再依次匹配 llow 等情况;
4、遇到不匹配的情况,就必须回溯到⼀开始搜索的 div 或者 p 节点,然后去搜索下个节点,重复这样的过程。
这样的搜索过程对于⼀个只是匹配很少节点的选择器来说,效率是极低的,因为我们花费了⼤量的时间在回溯匹配不符合规则的节点。
如果换个思路,我们⼀开始过滤出跟⽬标节点最符合的集合出来,再在这个集合进⾏搜索,⼤⼤降低了搜索空间。来看看从右到左来解析选择器:
1、⾸先就查到 的元素;
2、紧接着我们判断这些节点中的前兄弟节点是否符合 P 这个规则,这样就⼜减少了集合的元素,只有符合当前的⼦规则才会匹配再上⼀条⼦规则。
结果显⽽易见了,众所周知,在 DOM 树中⼀个元素可能有若⼲⼦元素,如果每⼀个都去判断⼀下显然性能太差。⽽⼀个⼦元素只有⼀个⽗元素,所以起来⾮常⽅便。
试想⼀下,如果采⽤从左⾄右的⽅式读取 CSS 规则,那么⼤多数规则读到最后(最右)才会发现是不匹
配的,这样会做费时耗能,最后有很多都是⽆⽤的;⽽如果采取从右向左的⽅式,那么只要发现最右边选择器不匹配,就可以直接舍弃了,避免了许多⽆效匹配。
浏览器 CSS 匹配核⼼算法的规则是以 从右向左⽅式匹配节点的。这样做是为了减少⽆效匹配次数,从⽽匹配快、性能更优。
四、CSS 语法解析过程
CSS 样式表解析过程中讲解的很细致,这⾥我们只看 CSS 语法解释器,⼤致过程如下:
1、先创建 CSSStyleSheet 对象。将 CSSStyleSheet 对象的指针存储到 CSSParser 对象中。
2、CSSParser 识别出⼀个 simple-selector ,形如 “div” 或者 “.class”。创建⼀个 CSSParserSelector 对象。
3、CSSParser 识别出⼀个关系符和另⼀个 simple-selecotr ,那么修改之前创建的 simple-selecotr, 创建组合关系符。
4、循环第3步直⾄碰到逗号或者左⼤括号。
5、如果碰到逗号,那么取出 CSSParser 的 reuse vector,然后将堆栈尾部的 CSSParserSelector 对象弹出存⼊ Vecotr 中,最后跳转⾄第2步。如果碰到左⼤括号,那么跳转⾄第6步。
6、识别属性名称,将属性名称的 hash 值压⼊解释器堆栈。
7、识别属性值,创建 CSSParserValue 对象,并将 CSSParserValue 对象存⼊解释器堆栈。
8、将属性名称和属性值弹出栈,创建 CSSProperty 对象。并将 CSSProperty 对象存⼊ CSSParser 成员变量m_parsedProperties 中。
9、如果识别处属性名称,那么转⾄第6步。如果识别右⼤括号,那么转⾄第10步。
10、将 reuse vector 从堆栈中弹出,并创建 CSSStyleRule 对象。CSSStyleRule 对象的选择符就是 reuse vector, 样式值就是CSSParser 的成员变量 m_parsedProperties 。
11、把 CSSStyleRule 添加到 CSSStyleSheet 中。
12、清空 CSSParser 内部缓存结果。
13、如果没有内容了,那么结束。否则跳转值第2步。
五、内联样式如何解析?
通过上⽂的了解,我们知道,当 CSS Parser 解析完 CSS 脚本后,会⽣成 CSSStyleSheetList ,他保存在Document 对象上。为了更快的计算样式,必须对这些 CSSStyleSheetList 进⾏重新组织。
计算样式就是从 CSSStyleSheetList 中出所有匹配相应元素的 property-value 对。匹配会通过CSSSelector 来验证,同时需要满⾜层叠规则。
将所有的 declaration 中的 property 组织成⼀个⼤的数组。数组中的每⼀项纪录了这个 property 的selector,property 的值,权重(层叠规则)。
可能类似如下的表现:
p > a {
color : red;
background-color:black;
}
css选择器分为哪几类
a {
color : yellow
}
div {
margin : 1px;
}
重新组织之后的数组数据为(weight我只是表⽰了他们之间的相对⼤⼩,并⾮实际值。)
selector selector weight
a color:yellow1
p > a color:red2
p > a background-color:black2
div margin:1px3
好了,到这⾥,我们来解决上述问题:
⾸先,要明确,内敛样式只是 CSS 三种加载⽅式之⼀;
其次,浏览器解析分为两个分⽀,HTML Parser 和 CSS Parser,两个 Parser 各司其职,各尽其责;
最后,不同的 CSS 加载⽅式产⽣的 Style rule ,通过权重来确定谁覆盖谁;
到这⾥就不难理解了,对浏览器来说,內联样式与其他的加载样式⽅式唯⼀的区别就是权重不同。
六、何谓 computedStyle ?
到这⾥,你以为完了?Too young too simple, sometimes naive!
浏览器还有⼀个⾮常棒的策略,在特定情况下,浏览器会共享 computedStyle,⽹页中能共享的标签⾮常多,所以能极⼤的提升执⾏效率!如果能共享,那就不需要执⾏匹配算法了,执⾏效率⾃然⾮常⾼。
也就是说:如果两个或多个 element 的 computedStyle 不通过计算可以确认他们相等,那么这些 com
putedStyle 相等的 elements 只会计算⼀次样式,其余的仅仅共享该 computedStyle 。
那么有哪些规则会共享 computedStyle 呢?
该共享的element不能有id属性且CSS中还有该id的StyleRule.哪怕该StyleRule与Element不匹配。
tagName和class属性必须⼀样;
mappedAttribute必须相等;
不能使⽤sibling selector,譬如:first-child, :last-selector, + selector;
不能有style属性。哪怕style属性相等,他们也不共享;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论