如何过滤QuerySelectorAll返回的元素

41

我正在开发一个 JavaScript 库,并使用此函数来匹配元素:

$ = function (a)
{
    var x;
    if (typeof a !== "string" || typeof a === "undefined"){ return a;}
    
    //Pick the quickest method for each kind of selector
    if(a.match(/^#([\w\-]+$)/))
    {
        return document.getElementById(a.split('#')[1]);
    }
    else if(a.match(/^([\w\-]+)$/))
    {
        x = document.getElementsByTagName(a);
    }
    else
    {
        x = document.querySelectorAll(a);
    }
    
    //Return the single object if applicable
    return (x.length === 1) ? x[0] : x;
};

有时我希望过滤这个函数的结果,比如挑选出一个 div span,或者一个 #id div 或其他相对简单的选择器。

我该如何过滤这些结果?我可以创建一个文档片段,并在该片段上使用 querySelectorAll 方法吗?还是必须采用手动字符串操作的方法?

我只关心现代浏览器和 IE8+。

如果您想查看我的整个库,可以点击此处:https://github.com/timw4mail/kis-js

编辑:

澄清一下,我想要能够做类似 $_(selector).children(other_selector) 这样的事情,并返回与该选择器匹配的子元素。

编辑:

所以这是我对最简单的选择器的潜在解决方案:

tag_reg = /^([\w\-]+)$/;
id_reg = /#([\w\-]+$)/;
class_reg = /\.([\w\-]+)$/;

function _sel_filter(filter, curr_sel)
{
    var i,
        len = curr_sel.length,
        matches = [];
    
    if(typeof filter !== "string")
    {
        return filter;
    }

    //Filter by tag
    if(filter.match(tag_reg))
    {
        for(i=0;i<len;i++)
        {
            if(curr_sell[i].tagName.toLowerCase() == filter.toLowerCase())
            {
                matches.push(curr_sel[i]);
            }
        }
    }
    else if(filter.match(class_reg))
    {
        for(i=0;i<len;i++)
        {
            if(curr_sel[i].classList.contains(filter))
            {
                matches.push(curr_sel[i]);
            }
        }
    }
    else if(filter.match(id_reg))
    {
        return document.getElementById(filter);
    }
    else
    {
        console.log(filter+" is not a valid filter");
    }
    
    return (matches.length === 1) ? matches[0] : matches;
    
}

它接受像 div、id 或类选择器这样的标签,并使用 curr_sel 参数返回匹配的元素。

我不想使用完整的选择器引擎,有更好的方法吗?


除了教育目的外,为什么您的库比现有的更好? - Raynos
@Raynos 我正在尝试创建一个极简、模块化的库,它不必处理IE6/7中JScript的愚蠢问题。 - timw4mail
我想这很独特。大多数库都处理IE6/7。 - Raynos
5个回答

35
注意:NodeList不是真正的数组,也就是说它没有像slice、some、map等数组方法。要将其转换为数组,请尝试使用Array.from(nodeList)。
参考:https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll 例如:
let highlightedItems = Array.from(userList.querySelectorAll(".highlighted"));

highlightedItems.filter((item) => {
 //...
})

33

我认为我没有正确理解这个问题。为什么你想要“过滤”querySelectorAll()的结果呢?实际上,它本身就是一种过滤器。如果你查询的是div span甚至更好的是#id div,那么得到的结果已经被过滤了,不是吗?

然而,你可以像下面这样将Array.prototype.filter应用于querySelectorAll的静态结果:

var filter   = Array.prototype.filter,
    result   = document.querySelectorAll('div'),
    filtered = filter.call( result, function( node ) {
        return !!node.querySelectorAll('span').length;
    });
那段代码首先会使用 querySelectorAll() 查找文档中所有的 <div> 节点。之后它将过滤出至少包含一个 <span><div> 节点。这段代码没有太多实际意义,只是为了演示用途(以防某些 Stack Overflow 用户想创建一个无用的评论)。 更新 你也可以使用 Element.compareDocumentPosition 进行过滤。它还会告诉你元素是否是 断开的跟随的前面的或者 包含的。详见 MDC .compareDocumentPosition()

1
有时候我需要获取当前元素的父元素或子元素。 - timw4mail
元素列表/节点列表是否总是继承自Array原型? - timw4mail
嗯...这很有帮助,但我仍然需要有节点进行比较。 - timw4mail
可能的用途之一是拥有多个不同的选择器(因为DOM不同),但您仍然希望从某些始终存在的元素中清除这些元素。当然,这看起来像是架构上出了问题,但我们都曾经历过这种情况... - joergipoergi
谢谢,这个答案让我意识到我应该使用querySelector来过滤,而不是两次过滤选择。 - FroboZ

18
2019年最简洁的方式是使用扩展语法...加上数组字面量[...],它与可迭代对象(如querySelectorAll返回的NodeList)非常配合使用:

[...document.querySelectorAll(".myClass")].filter(el=>{/*在此处编写您的代码*/})


看起来这是一种漂亮但昂贵的方法(在旧浏览器中)。 - Ivan

3

一些支持qsa的浏览器还支持非标准的matchesSelector方法,例如:

element.webkitMatchesSelector('.someSelector')

该方法将返回一个布尔值,表示element是否匹配所提供的选择器。因此,您可以迭代集合并应用该方法,保留积极的结果。

在没有matchesSelector的浏览器中,您可能需要构建自己的基于选择器的方法,类似于您正在构建的选择器引擎。


1
嗯,我更喜欢避免特定于浏览器的功能。 - timw4mail
@timw4mail:是的,但您可以测试浏览器特定功能是否存在,并在可用时使用它,否则请使用引擎。如果您尝试使用临时容器(如片段),则需要将元素从其位置中删除并放置在内部。这可能不是您想要的。 - user113716
@Raynos: :o) 这是相当新的。我在阅读 jQuery 发布说明时听说过它。我想他们说他们进行了游说。 - user113716
1
1.4.3发行说明中:*"jQuery项目请求浏览器添加新的matchesSelector方法(编写测试套件、与供应商交谈并提交错误报告),现在整个社区都可以获得出色的性能优势。"* - user113716

1
使用Array.from()filter()数组方法。
例如,对于设置在输入元素上的".myClass",并且您想要返回所有值不为空的输入。您可以使用以下方法:
Array.from(document.querySelectorAll(".myClass")).filter( el => el.value !== "")

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