DOM元素querySelectorAll可能让你意外的特性表现

这篇文章发布于 2015年11月5日,星期四,23:31,归类于 JS实例。 阅读 69299 次, 今日 4 次 32 条评论

 

一、时间紧急,废话少说

本文所在的页面藏匿了下面这些代码:

<img id="outside">
<div id="my-id">
    <img id="inside">
    <div class="lonely"></div>
    <div class="outer">
        <div class="inner"></div>
    </div>
</div>

就是下面这样的表现(为了便于观察,我加了边框背景色和文字):

孤家寡人
outer-inner之间

最内部

首先说点大家都知道的热热身。

  • querySelectorquerySelectorAll IE8+浏览器支持。
  • querySelector返回的是单个DOM元素;querySelectorAll返回的是NodeList.
  • 我们一般用的多的是document.querySelectorAll, 实际上,也支持dom.querySelectorAll.例如:
    document.querySelector("#my-id").querySelectorAll("img")

    选择的就是里面这个妹子。例如,我在控制台输出该选择NodeList的长度和id,如下截图:
    控制台输出的选择的图片结果

好了,上面都是众所周知的,好,下面开始展示点有意思的。

大家看下下面2行简单的查询语句:

document.querySelectorAll("#my-id div div");
document.querySelector("#my-id").querySelectorAll("div div");

一休哥 提问:上面两个语句返回的NodeList的内容是否是一样的?

给大家1分钟的时间思考下。

//zxx: 假设1分钟已经过去了

好了,答案是:不一样的。估计不少人跟我一样,会认为是一样的。

实际上:

document.querySelectorAll("#my-id div div").length === 1;
document.querySelector("#my-id").querySelectorAll("div div").length === 3;

大家如果有疑问,可以在控制台测试下,下图就是我自己测试的结果:
长度结果截图

为啥会这样?

第一个符合我们的理解,不解释。那下一个语句,为何返回的NodeList长度是3呢?

首先,遍历该NodeList会发现,查询的三个dom元素为:div.lonely, div.outer, div.inner.

奇怪,奇怪,怎么会是3个呢?

jQuery中有个find()方法,大家很可能受到这个方法影响,导致出现了一些认知的问题:

$("#my-id").find("div div").length === 1;

如果使用find方法,则是1个匹配;由于结构和作用类似,我们很自然疑问原生的querySelectorAll也是这个套路。真是太错特错!!

要解释,为何NodeList长度是3,只要一句话就可以了,我特意加粗标红:

CSS选择器是独立于整个页面的!

什么意思呢?比如说你在页面很深的一个DOM里面写上:

<style>
div div { }
</style>

整个网页,包括父级,只要是满足div div父子关系的元素,全部会被选中,对吧,这个大家应该都清楚的。

这里的querySelectorAll里面的选择器也同样是这也全局特性。document.querySelector("#my-id").querySelectorAll("div div")翻译成白话文就是:查询#my-id的子元素,同时满足整个页面下div div选择器条件的DOM元素们。

我们页面往上滚动看看原始的HTML结构,会发现,在全局视野下,div.lonely, div.outer, div.inner全部都满足div div这个选择器条件,于是,最终返回的长度为3.

二、:scope与区域选择限制

其实,要想querySelectorAll后面选择器不受全局影响,也是有办法的,就是使用目前还处于实验阶段:scope伪类,其作用就是让CSS是在某一范围内使用。此伪类在CSS中使用是大头,但是也可以在querySelectorAll语句中使用:

document.querySelector("#my-id").querySelectorAll(":scope div div");

兼容性如下:

:scope兼容性截图

我写此文时候是15年11月初,目前基本上就FireFox浏览器支持,我估计,以后,会支持越来越多的。为什么呢?

因为Web Components需要它,可以实现真正独立封装,不会受外界影响的HTML组件。

关于:scope目前支持尚浅,时机未到,我就没必要乱展开了,点到为止。

更新于2018-01-09
现在在看我2, 3年前说的上面这段话,发现脸打得啪啪响。:scope伪类看来是支持越来越少了。

更新于2019-02-09
又反转了,:scope伪类在CSS中虽然不支持,但是在querySelectorAll中是有效的。

三、结语还是要的

参考文章:querySelectorAll from an element probably doesn’t do what you think it does

感谢阅读,欢迎纠错,欢迎交流!

//众人: 你丫逗我呢,这也能叫结语……

(本篇完)

分享到:


发表评论(目前32 条评论)

  1. lwwen说道:

    又是一个知识点 谢谢大佬 虽然好像目前来说感觉好像用不上

  2. fengbo说道:

    :scope 特性已经被移除了
    https://caniuse.com/#search=scope

  3. 洪泽扬说道:

    感觉这个功能没有实际作用呀

  4. Rehack说道:

    没有谈到querySelector()的性能问题

  5. lilu说道:

    我试了下dom.querySelectorAll 控制台报错呢,说dom undefined

  6. ys说道:

    Element.querySelectorAll(‘#/reg’),为什么这条语句会报错呢?

  7. zhifeizhang说道:

    以前测试过这个问题,是在#my-id范围下的,包含#my-id本身。

  8. 陈小轩说道:

    document.querySelectorAll(“#my-id div div”) 这个没啥好解释的

    document.querySelector(“#my-id”).querySelectorAll(“div div”)

    1. 找出 #my-id 节点
    2. 在#my-id节点上(包含#my-id 节点),找符合 div div 选择器的节点
    $(‘#my-id’).find(‘div’).filter(‘div div’)

    • Cc说道:

      对~这个说法比较准确= = 我之前还在往外面包裹了一层div想看是不是真的会在全局下匹配div div结构的 = =

  9. zhoucumt说道:

    打卡3

  10. 杜宏伟说道:

    Element.querySelectorAll() 不是在整个页面查找的。

    https://developer.mozilla.org/zh-CN/docs/Web/API/Element/querySelectorAll

    返回一个non-live的NodeList对象,这个对象将会包含调用querySelectorAll()方法的那个DOM对象的所有后代元素中匹配指定css选择器的元素们.

    我也亲自做了实验,确实是这样的,你说的整个网页,包括父级,显示是错误的。

    验证页面 http://139.129.27.90/demo/o1.html

    • jin说道:

      博主的解释没错,指的是“查询#my-id的子元素,同时满足整个页面下div div选择器条件的DOM元素们。”

  11. butcherv说道:

    服了

  12. hellokit说道:

    定义document.querySelector(cssQuery) 范围为A
    jquery中使用find()查找的范围 是<A的,这个符合认知
    querySelectorAll()查找的范围是 <=A的,这个比较奇葩

  13. 黄小龙说道:

    console.log(document.querySelector(“#my-id”).querySelectorAll(“.m div”));
    //[div#n, div.lonely, div.outer, div.inner]
    由此证明不是全局范围下查找,而是相对于调用querySelectorAll的dom元素

    但是:
    console.log(document.querySelector(“#my-id”).querySelectorAll(“.m”));
    // []

    求解这是为什么,按照第一个console的逻辑来说,第二个console应该打印[div.m]啊

  14. 关于querySelectorAll说道:

    document.querySelector(“#my-id”).querySelectorAll(“div div”)翻译成白话文就是:查询#my-id的子元素,同时满足整个页面下div div选择器条件的DOM元素们

    结果是3说明是基于整个页面的说法有误吧?
    如果my-id外面套一层div那么结果应该是几呢?
    像这样:

    http://jsbin.com/meyiya/4/edit?html,js,console

    结果是不是四呢?非也非也,是3.
    结论是:通过DOM非document使用querySelectorAll 不是基于整个页面(可以理解为全局)的查找,而是基于当前的元素(从当前元素开始的向里面的局部)查找。

    • Learn说道:

      原文:“document.querySelector(“#my-id”).querySelectorAll(“div div”)翻译成白话文就是:查询#my-id的子元素,同时满足整个页面下div div选择器条件的DOM元素们。”

      querySelectorAll是基于整个页面的,但是前面的“querySelector(“#my-id”)”相当于把基于页面的选择结果过滤出来,限定在了自己的子元素下。博主的解读没有问题。

    • yangzongjun说道:

      没有问题的,有句“同时满足”,就是说querySelectorAll是基于全局查找,但是前面的querySelector限定了范围,相当于求二者的交集

  15. CarterLi说道:

    :scope选择器与scope属性不同,前者的支持度是很高的
    https://developer.mozilla.org/en-US/docs/Web/CSS/%3Ascope

  16. 2713说道:

    document.querySelector(‘selector0’).querySelector(‘selector1’)

    document=document.querySelector(‘selector0’)//这个document的根是body
    document.querySelector(‘selector1’)//这个document的根是selector0

  17. 北京今天又雾霾说道:

    我服了,html标签就是出不来,楼主能让人插插代码否。

    作者说:document.querySelector(“#my-id”).querySelectorAll(“div div”)翻译成白话文就是:查询#my-id的子元素,同时满足整个页面下div div选择器条件的DOM元素们。
    那么问题出来了,是这样吗?
    看如下代码:

    body下为如下结构:

    父00
    子001
    父div的id为my-id
    子“图片” id为inside
    子div的 class为lonely
    子div的 class为outer
    孙divclass为inner

    var km=document.querySelectorAll(“#my-id div div”);
    var km2=document.querySelector(“#my-id”).querySelectorAll(“div div”);
    var km3=document.getElementById(“my-id”).querySelectorAll(“div div”);
    var km4=document.querySelectorAll(“div div”);
    console.log(km,km2,km3);
    console.log(km4)
    结果是:
    [div.inner] [div.lonely, div.outer, div.inner] [div.lonely, div.outer, div.inner]
    [div, div.lonely, div.outer, div.inner]

    也就是说#my-id下的div div关系,而不是整个页面的。否则km3和km4为什么不一样呢..00和001也是div div关系

    请把我发的其余评论删除。。

  18. muzuiget说道:

    涨姿势了,一直没留意,大概都是一直用精确的 class 名来选择子元素的缘故吧。

    > 翻译成白话文就是:查询#my-id的子元素,同时满足整个页面下div div选择器条件的DOM元素们。

    因为这两个选择器都挨在同一行,如果隔开很多,这样说的话就很纠结了,例如

    var node = document.querySelector(“#my-id”);

    // 隔了很多行代码

    node.querySelectorAll(“div div”);

    因为 css 匹配是自下而上(或者说自右而左),所以单独拿 node.querySelectorAll(“div div”); 这行来解释就好了。

    1. 拿出 node 元素下所有子元素,处理对象是所有子元素,这时候就没有 node 这个元素本身的事了。
    2. 拿出一个 div(处理选择器右边那个 div),再看看它的父元素是不是也是一个 div(处理选择器右边那个 div),如果是,匹配起来。

  19. mantou说道:

    Web Components 默认 就是不影响 外界 样式的。
    因为 组件里面的 style是在 shadow DOM 中的。
    只有 在组件中写 特殊 选择器 :host 才会选到外面的。

    同样 外面的不能选里面的 ,可以使用 /deep/ ^ ^^ 深选择器选择 shadow中的元素。

    http://mantou132.github.io/test/shadow%20DOM.html

  20. Bob说道:

    太细节了,长姿势了。

  21. chang1ng说道:

    期待更多关于scope的知识

  22. IntPtr说道:

    https://dom.spec.whatwg.org/#element-collections
    新规范有两个函数, element.query 和 element.queryAll, 就是基于 :scope 的.