CSS选择器的解析顺序为什么要从右到左?
浏览器在解析网页的过程中,先会生成dom树和cssom树,最终将两棵树合并成为一颗render tree。在合并的时候,需要为每一个dom元素找到对应的样式,但是dom元素和cssom树中的样式并不是一一对应的关系,所以只能使用最笨的办法:针对每一个CSS样式类去遍历dom树,找出作用的dom节点。
由于页面中的dom节点是非常多的,而且css选择器与dom节点的对应关系是多对多,而且在匹配的时候还得遍历树,整个过程是一个非常耗时的过程。
如果是从左到右顺序解析
假设有一个选择器是
div > div.demo p span.red{}
如果按照从左到右的规则去匹配,大概的过程应该是这样的:
- 首先找到所有的div元素,假设存在arr1中(需要保留起来,因为有回溯)
- 从arr1中拿出一个div,找到该div的所有含有.demo的子div,存在arr2中。如果找不到,重复第2步
- 从arr2中找到获取一个div,查找它后代所有p,存在arr3中。如果找不到,重复第3步
- 从arr3中获取一个p,查找它后代所有含有.red的span,如果有则就是匹配的dom节点。如果没有重复第4步
- 如果一个数组遍历完之后,依次向上一步回溯
可以看出,这个过程中会遍历到很多的不匹配的节点,造成了很多的资源浪费,增加了计算的次数,可能会造成页面的卡顿。
如果是从右到左顺序解析
依然是这个选择器:
div > div.demo p span.red{}
如果是从右到左的方式遍历,大概的过程是这样的:
- 先找出所有含有.red的span,存在arr1中,这一步就会减少很多的无用遍历路径
- 从arr1拿出一个span,然后找到祖先中有p的span,将这些p存入arr2中。如果没有,重复第二步
- 从arr2获取一个p,查找p的祖先中是否含有div.demo,如果有则将这些div存入arr3。如果没有重复第三步
- 从arr3中获取一个div,查找该div的父节点是不是div,如果是的话,这条路径的span节点就是匹配的节点。如果不是,重复第4步
- 如果一个数组遍历完之后,依次向上一步回溯
可以看到,从右到左的匹配过程中,每一次匹配相对于从左到右的方式来说,都直接去掉了很多不符合条件的路径,减少了很多不必要节点的遍历。
分析
本质上就是有一条不完整的节点路径,然后让你从dom树中找出所有的匹配的路径
从左到右
就是从祖先节点遍历到叶子节点的过程
从右到左
就是从叶子节点遍历到祖先节点的过程
从左到右的方式中,大多数路径都是读到最后才发现不匹配。而从右到左的方式中,只要右选择器不匹配,就直接去掉这条路径,会减少很多无效的匹配。
从算法的角度看,这两种方式的时间复杂度是一样的,在极端的情况下,表现出的性能是一样的。
但是考虑到现实的情况,组合选择器层级越深,匹配的节点就越具体,匹配的数量就越小,这样从右向左查找可以在一开始就去掉那些不匹配的路径,减低查找的次数。
