ul li a span 这种选择器的写法有什么问题?

discuz的文档中有这么一句话:

勿使用冗余低效的 CSS 写法, 例如:ul li a span { ... }

http://faq.comsenz.com/librar...

不知道为什么这种写法会是冗余低效?

如果说直接使用标签选择器不好,那么改成这样呢:
ul.test li a span

关于选择器的层级问题,我不理解为什么超过三级会不好?

补充:
我作了个测试:

<div class="row">
    <div class="haha">
    <ul class="rul">
      <li class="rli"><span class="ss" id="hi">你们好</span></li>
    </ul>
  </div>
</div>    


<script>
//选择器性能测试
function test(fn){
  var start=+new Date();
  for(var i=0;i<1000000;i++){
   fn(); 
  }
  var re=+new Date()-start;
  console.log("用时:"+re);
}

function a(){
//                                                  $(jquery)--document.querySelector         
//document.querySelector("div div ul li span");        //2349--505
//document.querySelector("div.row div ul li span");    //2292--515
//document.querySelector("div.row>div>ul>li>span");    //2340--535
//document.querySelector("div.row .haha .rul .rli .ss");//2273--535
//document.querySelector(".row .ss");                   //2106--440
//document.querySelector(".ss");                       //1096--120
//document.querySelector("span.ss");                    //2060--350
//document.querySelector("#hi");                        //535--116
//document.querySelector("span#hi");                    //1895--150
}

test(a);
</script>

我作了上面的测试,结论是:
1.性能最优的选择器是:id与class ,id 略快一些,但页面中每多一个id就会在js中多一个全局变量,显然用id作选择器是不好的。

2.span#hi性能比#hi差,我看过一些文章说前者会更好,但从测试的结果来看并没有。

3.层级多的选择器,无论是用标签还是用class,性能都比较差。这点以下很多回答也都提到了,但我还是不太明白,这与我的直觉相矛盾。
按理说层级多,实际上缩小了查找的范围,不应该查找速度更快才对吗?

打个比喻:在某个区域的某个楼层的某个方向的某个房间里有个叫做"haha"的人,你要怎样才能更快地找到它?
是只告诉你它的名字,
还是告诉你它所在的区域,楼层,方向,房间号,再去查找会比较快?

另外,很多人提到关于代码的可维护性问题,我觉得这是因人而异的吧,我觉得添加一些id与class写起来更方便,多层级也更方便定位css的作用区域,更易于维护。
比如css中存在这两种代码:
A:
.content{}
.title{}
.p{}
.li{}

B:
.content{}
.content .title{}
.content p{}
.content p li{}

不是很明显B这种写法会更方便维护吗?而且可以更好地控制css代码的作用域,不是吗?

阅读 6.8k
5 个回答

说一下规则匹配成功的过程,你试着模拟一遍:

  1. 浏览器解析到一个span element, 并且发现css规则里面有一个写着span的规则 ul li a span,开始尝试是否匹配成功

  2. 从span开始往父层上找,看看能否匹配到a (还不需要是直接父结点,只要链上有a就算,所以会一直往上匹配直到html或者浏览器剪枝发现已经不可能匹配为止)

  3. 找li(同上)

  4. 找ul(同上)

所以这个算法复杂度是指数级的
假设写到后面你的页面有大约2000个elements同时存在(SPA中型差不多就这个大小),大概有15层element嵌套,
第15层节点中有100个左右的span,那确定不能匹配a最多要14层查找,li 13层, ul 12层,再乘上span自己本身匹配的计算,这个页面交互基本就卡住了,就算不考虑交互的静态页面,这个运算量也是完全应该去避免的

看到有几位同学回答说是选择器太长会影响性能。没错,性能是其中的一个方面,但是以现在浏览器的性能是不会因为几个过长的选择器而产生性能问题的。之所以不推荐这么写,主要原因还是要从 CSS 代码的可维护性的方面进行考虑。

还是来一个例子吧,假设你需要展示一个新闻列表,你可以会写下面的结构:

<ul>
    <li><a href="#"><span>杨幂被网友批“下巴前倾像乌龟”,真相曝光很心酸</span></a></li>
    <li><a href="#"><span>大S带孩子累出心脏病 妹妹小S:现在已经好多了</span></a></li>
    <li><a href="#"><span>不错哟!林妙可被曝通过中国音乐学院美声二试</span></a></li>
    <li><a href="#"><span>永远十七岁!林志颖出道25周年容颜几乎未改</span></a></li>
</ul>
ul {
    padding: 10px;
}

ul li {
    list-style: none;
}

ul li a {
    text-decoration: none;
}

ul li a:hover {
    text-decoration: underline;
}

ul li a span {
    color: #333;
    font-size: 14px;
}

这样写有没有问题?答案是没有明显的问题。它实现了我们想要的样式,至少,它是 work 的。

但是,我们说,这样写不是最好的。

首先,这样写把 CSS 的选择器和 html 结构耦合在了一起。意思是,如果这样写 CSS 选择器,对应的我们的 html 结构只能是上面例子中的这种,如果要改一个标签,还要去对应的样式文件中去修改选择器。

举个例子:如果哪一天,需求变了,这个新闻列表变成了一个热门新闻排行榜,为了更好的兼顾 html 语义,我们需要把 ul 标签(无序列表)换成 ol 标签(有序列表)。这个时候,我们样式文件中的选择器就都失效了。

那么,改成 class 就会解决这个问题吗?下面我们就看一下改成 class 的情况:

先来个简单点的,只给 ul 添加一个 class 属性,其他的保持不变。

<ul class="news-list">
    <li><a href="#"><span>杨幂被网友批“下巴前倾像乌龟”,真相曝光很心酸</span></a></li>
    <li><a href="#"><span>大S带孩子累出心脏病 妹妹小S:现在已经好多了</span></a></li>
    <li><a href="#"><span>不错哟!林妙可被曝通过中国音乐学院美声二试</span></a></li>
    <li><a href="#"><span>永远十七岁!林志颖出道25周年容颜几乎未改</span></a></li>
</ul>

那么,我们对应的 CSS 就可以改成下面这样了。

.news-list {
    padding: 10px;
}

.news-list li {
    list-style: none;
}

.news-list li a {
    text-decoration: none;
}

.news-list li a:hover {
    text-decoration: underline;
}

.news-list li a span {
    color: #333;
    font-size: 14px;
}

现在,我们可以放心的替换 ul 标签,而不会影响到列表的样式了。

同样的,我认为,我们没有必要使用 li 这个标签选择器,它限制了列表中的每一项必须使用 li 标签。在这里就不过多展开了,感兴趣的话可以找一些 BEM 相关的内容看看,例如这篇

另一个比较容易理解的原因是,如果页面中还有其他的列表,也使用了 ul -> li -> a -> span 的结构,像我们上面这样写选择器就会「误伤」其他的列表,影响它们的样式。


至于题主提到的为什么选择器的层级不建议超过三层,我在这里做个简单的解释:

过多的层级会导致选择器越来越长,以至难以维护。

举个例子,如果我们的页面中有多个新闻列表,使用的是上面例子中的那个 html 结构,现在有个需求是把其中的某个列表的文字颜色改成灰色(#666),要怎么做?要么加长选择器的长度,要么使用一个 id 选择器或者 class 选择器。总之,就是想办法写一个 specificity 值比原来选择器的 specificity 值更大的选择器来覆盖它。久而久之,选择器就会变得越来越长,越来越难以维护。

ul li a span 这个选择器来说,我觉得可以简写成 ul span,同样的,ul li a 可以简写成 ul a,中间多个的那个 li 有什么用呢?当然啦,如果能换成 class 选择器就更好了。

  1. 写代码,应本着尽量精简够用的原则。如果能用一行代码实现的,不使用两行。

  2. 这个问题也是同理。如果能使用这个选择器去选择元素,说明这个选择器不够高效。为什么不够高效?只要计算的步骤多,就会占用计算资源(时间、内存等)。题主可以想一下你想要选中的元素,一定不用上面四个标签的方法也可以获取到。

举个例子,如果让你用JS(不能用querySelector)获取你要的元素你会实现?

你应该不会一层一层的去查找,肯定会想着用最少的计算量来达到目的。css选择器背后的代码实现也是如此,你的选择器就是参数,传入的参数越多,解析就越慢。

之所以建议css选择器的层级不要过多的原因在我看来主要有以下三点:
一 层级太多会造成代码冗余,违背了代码简洁的规范,也会造成文件过大;
二 层级太多会造成代码可读性差,不方便他人阅读;
三 浏览器的css解析是从右到左解析的,浏览器先会找出最右侧选择器的所有匹配元素,然后再逐一往左解析根据选择器筛选。因此css层级过多会造成解析时间过长,影响页面性能!

而之所以建议不使用标签选择器也是因为匹配的元素过多,因此建议使用类选择器和id选择器精确匹配

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题