querySelector搜索立即子代。

137

我有一个类似于jQuery的函数:

function(elem) {
    return $('> someselector', elem);
};

问题是如何使用querySelector()实现同样的效果?

querySelector()中的>选择器需要显式指定父级。是否有任何解决方法?

10个回答

194

虽然这不是完整的答案,但你应该关注W3C选择器API v.2,它已经在大多数桌面和移动浏览器中提供,除了IE(Edge似乎支持):查看完整的支持列表

function(elem) {
  return elem.querySelectorAll(':scope > someselector');
};

16
这是正确的答案。然而,浏览器支持有限,如果想要使用它则需要一个shim。我为此构建了scopedQuerySelectorShim - lazd
3
当前规范中已经删除了:scope属性。 - tobi
8
实际上,:scope 仍然在 https://drafts.csswg.org/selectors-4/#the-scope-pseudo 中有定义。目前只是 <style scoped> 属性被移除了;不清楚这是否也会导致 :scope 被移除(因为 <style scoped>:scope 的一个重要用例)。 - John Mellor
2
真让人惊讶:只有Edge不支持这个 :) - Xenos
13
2021年,所有现代浏览器均支持此功能。 - DisgruntledGoat
显示剩余2条评论

36
你做不到。没有选择器可以模拟你的起点。
jQuery的做法是(更多是因为他们不喜欢qsa的行为方式),他们会检查elem是否有一个ID,如果没有,他们会临时添加一个ID,然后创建一个完整的选择器字符串。
基本上,你需要这样做:
var sel = '> someselector';
var hadId = true;
if( !elem.id ) {
    hadID = false;
    elem.id = 'some_unique_value';
}

sel = '#' + elem.id + sel;

var result = document.querySelectorAll( sel );

if( !hadId ) {
    elem.id = '';
}

这肯定不是jQuery代码,但据我所记,它基本上就是他们所做的事情。不仅在这种情况下,而且在任何情况下,当您从嵌套元素的上下文中运行选择器时。

Sizzle源代码


2
写作时是正确的,但请参考@avetisk的答案获取更新的方法。 - lazd

28

完整的:scope填充

正如avetisk提到的那样,Selectors API 2使用:scope伪选择器。
为了使这在所有支持querySelector的浏览器中工作,这里提供了填充。

(function(doc, proto) {
  try { // check if browser supports :scope natively
    doc.querySelector(':scope body');
  } catch (err) { // polyfill native methods if it doesn't
    ['querySelector', 'querySelectorAll'].forEach(function(method) {
      var nativ = proto[method];
      proto[method] = function(selectors) {
        if (/(^|,)\s*:scope/.test(selectors)) { // only if selectors contains :scope
          var id = this.id; // remember current element id
          this.id = 'ID_' + Date.now(); // assign new unique id
          selectors = selectors.replace(/((^|,)\s*):scope/g, '$1#' + this.id); // replace :scope with #ID
          var result = doc[method](selectors);
          this.id = id; // restore previous id
          return result;
        } else {
          return nativ.call(this, selectors); // use native code for other selectors
        }
      }
    });
  }
})(window.document, Element.prototype);

使用方法

node.querySelector(':scope > someselector');
node.querySelectorAll(':scope > someselector');

出于历史原因,我的以前的解决方案

根据所有答案

// Caution! Prototype extending
Node.prototype.find = function(selector) {
    if (/(^\s*|,\s*)>/.test(selector)) {
        if (!this.id) {
            this.id = 'ID_' + new Date().getTime();
            var removeId = true;
        }
        selector = selector.replace(/(^\s*|,\s*)>/g, '$1#' + this.id + ' >');
        var result = document.querySelectorAll(selector);
        if (removeId) {
            this.id = null;
        }
        return result;
    } else {
        return this.querySelectorAll(selector);
    }
};

使用方法

elem.find('> a');

Date.now() 不被旧版浏览器支持,可以用 new Date().getTime() 替代。 - Christophe

9

如果你想最终找到直接子元素(而不是例如> div > span),你可以尝试使用Element.matches()

const elem = document.body
const elems = [...elem.children].filter(e => e.matches('b'))

console.log(elems)
<a>1</a>
<b>2</b>
<b>3</b>
<b>4</b>
<s>5</s>


6

声明

个人认为,我会采纳Patrick Dw的答案,并点赞他的回答。我的回答是为了寻求替代方案。我认为这并不值得被踩。

以下是我的尝试:

function q(elem){
    var nodes = elem.querySelectorAll('someSeletor');
    console.log(nodes);
    for(var i = 0; i < nodes.length; i++){
        if(nodes[i].parentNode === elem) return nodes[i];
    }
}

see http://jsfiddle.net/Lgaw5/8/


1
有几个问题。 @disfated 希望它能与 querySelector 结合使用,这意味着无法使用 :eq()。即使可以,您的选择器也将返回 :eq() 到其在页面上的外观的元素,而不是其父级的索引(这就是您获取idx 的地方)。 - user113716
不幸的是,这也行不通。当选择器从父级上下文运行时,它从最左边的子元素开始,并找到所有匹配的元素,无论嵌套多深,然后继续执行。所以说,如果elem在索引4处,但有一个先前的兄弟节点具有不同的tagName,但它内部嵌套了一个具有匹配的tagName的元素,则该嵌套元素将在您要定位的元素之前包含在结果中,再次扰乱索引。这里有一个例子 - user113716
是的,但您需要硬编码该单个增量。这将需要先了解它。如果我添加另一个类似的元素,它会再次出现问题。 ;o) http://jsfiddle.net/Lgaw5/3/ - user113716
好的,不过那只是一些替代解决方案的想法,即使它不是很好,也不应该被点踩,对吧? - Liangliang Zheng
你忘记发布链接了。;o) ...啊,你一定是指你的更新答案。:op - user113716
显示剩余5条评论

5
如果您知道您要查看的元素的标签名称,那么您可以在选择器中使用它来实现想要的效果。
例如,如果您有一个包含<option><optgroup><select>,并且您只想获取其直接子级的<option>,而不是<optgroup>中的子级:
<select>
  <option>iPhone</option>
  <optgroup>
    <option>Nokia</option>
    <option>Blackberry</option>
  </optgroup>
</select>

所以,如果您有选择元素的引用,您可以通过以下方式-令人惊讶地-获取它的直接子元素:

selectElement.querySelectorAll('select > option')

它似乎在Chrome、Safari和Firefox中运行良好,但没有在IE中进行测试。 =/


10
警告:此答案实际上并未限制作用域。如果模式在更深的嵌套级别中重复,即使它不是直接子元素,仍将被选中。`<ul id="start"> <li>A</li> <li> <ul> <li>b</li> <li>c</li> </ul> </li></ul> var result = document.getElementById('start').querySelectorAll('ul>li');` -> 所有4个li元素都将被选中! - Risord
1
天哪,如果在同一父元素中有两个<select>元素,那就糟糕了。 - Tomáš Zato

2
以下是一种简化的通用方法,用于仅在直接子元素上运行任何CSS选择器查询 - 它还考虑了组合查询,例如"foo[bar], baz.boo":
var count = 0;
function queryChildren(element, selector) {
  var id = element.id,
      guid = element.id = id || 'query_children_' + count++,
      attr = '#' + guid + ' > ',
      selector = attr + (selector + '').replace(',', ',' + attr, 'g');
  var result = element.parentNode.querySelectorAll(selector);
  if (!id) element.removeAttribute('id');
  return result;
}


*** Example Use ***

queryChildren(someElement, '.foo, .bar[xyz="123"]');

2

有一个名为query-relative的库,它是查询选择器(query-selector)的方便替代品。它在子元素选择器'> *':scope(包括IE8)上提供了polyfill,同时规范化了:root选择器。 此外,它还提供了一些特殊的相对伪类,例如:closest:parent:prev:next,以备不时之需。


1

这个对我有用:

Node.prototype.search = function(selector)
{
    if (selector.indexOf('@this') != -1)
    {
        if (!this.id)
            this.id = "ID" + new Date().getTime(); 
        while (selector.indexOf('@this') != -1)
            selector = selector.replace('@this', '#' + this.id);
        return document.querySelectorAll(selector);
    } else 
        return this.querySelectorAll(selector);
};

当您想搜索直接子元素时,必须在“>”之前通过@this关键字。



0

检查元素是否有ID,否则添加随机ID并基于此进行搜索

function(elem) {
      if(!elem.id)
          elem.id = Math.random().toString(36).substr(2, 10);
      return elem.querySelectorAll(elem.id + ' > someselector');
    };

将会执行与之前相同的操作

$("> someselector",$(elem))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接