在jQuery.find()中跳过选择器的递归?

时间:2014-06-11 09:35:52

标签: javascript jquery recursion traversal jquery-traversing

TL; DR:我如何获得像find()这样的动作,但阻止特定选择器的遍历(不是完全停止,只是跳过)?

答案:$(Any).find(Selector).not( $(Any).find(Mask).find(Selector) )

  

有许多真正伟大的答案,我希望我可以更多地分配赏金点数,也许我应该为这些中的一些做出50 pt奖励; p我选择Karl-André Gagnon's因为这个答案设法使findExclude不需要在一个稍长的行中。虽然这使用了三个查找调用和一个重度非过滤器,但在大多数情况下,jQuery可以使用非常快速的实现来跳过大多数find()的遍历。

     

下面列出了特别好的答案:

     

falsarellamy solution,findExclude(),在许多情境中表现最佳

     

Zbyszek:基于过滤器的解决方案类似于falsarella' s,效率也很好

     

Justin:针对底层问题的完全不同但可管理且功能性的解决方案

     

每一项都有其独特的优点,值得一提。

我需要完全下降到一个元素并比较选择器,将所有匹配的选择器作为数组返回,但在遇到另一个选择器时跳过下降到树中。

Selection Path Illustration 修改:将原始代码示例替换为我网站上的一些

这是一个消息论坛,可能有回复消息组嵌套在任何消息中。
但请注意,我们不能使用消息或内容类,因为该脚本也用于论坛之外的其他组件。只有InterfaceGroupInterfacecontrols类可能有用 - 最好只是界面和控件。

Interact with the code and see it in JS Fiddle, thanks Dave A, here在查看JavaScript控制台时单击按钮,以查看每个.Interface嵌套级别的控件类被绑定一个额外的时间。

Visual A,Forum Layout Struture:

    <li class="InterfaceGroup">
        <ul class="Interface Message" data-role="MessagePost" >
            <li class="instance"> ... condensed ... </li>
            <li class="InterfaceGroup"> ... condensed ...</li>
        </ul>
        <ul class="Interface Message" data-role="MessagePost" >
            <li class="instance"> ... condensed ... </li>
        </ul>
        <ul class="Interface Message" data-role="MessagePost" >
            <li class="instance"> ... condensed ... </li>
            <li class="InterfaceGroup"> ... condensed ...</li>
        </ul>

    </li>

在每个<li class="InterfaceGroup">内部,可以有相同结构的任意数量的重复(每个组是消息的线程)和/或更深的嵌套,例如..

    <li class="InterfaceGroup">

        <ul class="Interface Message" data-role="MessagePost" >
            <li class="instance"> ... condensed ... </li>
            <li class="InterfaceGroup">

                <ul class="Interface Message" data-role="MessagePost" >
                    <li class="instance"> ... condensed ... </li>
                    <li class="InterfaceGroup"> ... condensed ...</li>
                </ul>
            </li>
        </ul>
    </li>

在每个<li class="instance"> ... </li>内部,有一个由class="controls"可能出现的另一个团队决定的任意位置,并且应绑定一个事件监听器。虽然这些包含消息,但其他组件会随意构建其标记,但在.controls内始终会.Interface,这些.InterfaceGroup被收集到<ul class="Interface Message" data-role="MessagePost" > <li class="instance"> <ul class="profile"> ...condensed, nothing clickable...</ul> <ul class="contents"> <li class="heading"><h3>Hi there!</h3></li> <li class="body"><article>TEST Message here</article></li> <li class="vote controls"> <button class="up" data-role="VoteUp" ><i class="fa fa-caret-up"> </i><br/>1</button> <button class="down" data-role="VoteDown" >0<br/><i class="fa fa-caret-down"> </i></button> </li> <li class="social controls"> <button class="reply-btn" data-role="ReplyButton" >Reply</button> </li> </ul> </li> <li class="InterfaceGroup" > <!-- NESTING OCCURRED --> <ul class="Interface Message" data-role="MessagePost" > <li class="instance">... condensed ... </li> <li class="InterfaceGroup" >... condensed ... </li> </ul> </li> </ul> 。内容的复杂度降低版本(对于论坛帖子)在下面供参考。

Visual B,带控件类的消息内容:

instance

我们只能绑定到接口类中的控件.controls可能存在,也可能不存在,但接口会存在。 事件会向.Interface元素冒泡,并引用保留它们的$('.Interface').each( bind to any .controls not inside a deeper .Interface )

所以我正在尝试.Interface .controls

这是棘手的部分,因为

  • .control会在.each()
  • 中多次选择相同的var Interface=function() { $elf=this; $elf.call= { init:function(Markup) { $elf.Interface = Markup; $elf.Controls = $(Markup).find('.controls').not('.Interface .controls'); $elf.Controls.on('click mouseenter mouseleave', function(event){ $elf.call.events(event); }); return $elf; }, events:function(e) { var classlist = e.target.className.split(/\s+/), c=0, L=0; var role = $(e.target).data('role'); if(e.type == 'click') { CurrentControl=$(e.target).closest('[data-role]')[0]; role = $(CurrentControl).data('role'); switch(role) { case 'ReplyButton':console.log('Reply clicked'); break; case 'VoteUp':console.log('Up vote clicked'); break; case 'VoteDown':console.log('Down vote clicked'); break; default: break; } } } } }; $(document).ready( function() { $('.Interface').each(function(instance, Markup) { Markup.Interface=new Interface().call.init(Markup); }); } );
  • .not(&#39; .Interface .Interface .controls&#39;)取消任何更深层次的嵌套控件

我怎样才能使用jQuery.find()或类似的jQuery方法呢?

我一直在考虑,或许,使用不是选择器的孩子可以工作,而可以做与在引擎盖下找到相同的事情,但我&#39 ;我不确定它实际上是否会导致可怕的表现。答案仍然可以接受。儿童有效是可以接受的。

更新:最初我尝试使用psuedo-example来简洁,但希望看到一个论坛结构将有助于澄清问题,因为它们是自然嵌套的结构。下面我还发布了部分javascript供参考,init函数的第二行是最重要的。

缩减JavaScript部分:

{{1}}

7 个答案:

答案 0 :(得分:6)

如果要在find中排除元素,可以使用not filter。例如,我已经采取了排除元素的功能并缩短了时间:

$.fn.findExclude = function( Selector, Mask,){
    return this.find(Selector).not(this.find(Mask).find(Selector))
}

现在,对你说实话,我并不完全明白你想要什么。但是,当我看看你的功能时,我看到了你想要做的事情。

无论如何,看看这个小提琴,结果与你的相同:http://jsfiddle.net/KX65p/8/

答案 1 :(得分:4)

好吧,我真的不想在赏金上回答我自己的问题,所以如果有人能提供更好的或替代的实施,请做..

然而,为了完成这个项目,我最终在这方面做了很多工作,并提出了一个相当干净的jQuery插件,用于执行jQuery.find()样式搜索,同时从结果中排除子分支

用于处理嵌套视图中的元素集:

// Will not look in nested ul's for inputs
$('ul').findExclude('input','ul');

// Will look in nested ul's for inputs unless it runs into class="potato"
$('ul').findExclude('input','.potato');

http://jsfiddle.net/KX65p/3/找到更复杂的例子,我将它用于.each()一个嵌套类,并将每个嵌套视图中出现的元素绑定到一个类。这让我让组件服务器端和客户端反映彼此的属性,并有更便宜的嵌套事件处理。

<强>实施

// Find-like method which masks any descendant
// branches matching the Mask argument.
$.fn.findExclude = function( Selector, Mask, result){

    // Default result to an empty jQuery object if not provided
    result = typeof result !== 'undefined' ?
                result :
                new jQuery();

    // Iterate through all children, except those match Mask
    this.children().each(function(){

        thisObject = jQuery( this );
        if( thisObject.is( Selector ) ) 
            result.push( this );

        // Recursively seek children without Mask
        if( !thisObject.is( Mask ) )
            thisObject.findExclude( Selector, Mask, result );
    });

    return result;
}

(简明版)

$.fn.findExclude = function( selector, mask, result )
{
    result = typeof result !== 'undefined' ? result : new jQuery();
    this.children().each( function(){
        thisObject = jQuery( this );
        if( thisObject.is( selector ) ) 
            result.push( this );
        if( !thisObject.is( mask ) )
            thisObject.findExclude( selector, mask, result );
    });
    return result;
}

答案 2 :(得分:2)

也许这样的事情会起作用:

$.fn.findExclude = function (Selector, Mask) {
    var result = new jQuery();
    $(this).each(function () {
        var $selected = $(this);
        $selected.find(Selector).filter(function (index) {
            var $closest = $(this).closest(Mask);
            return $closest.length == 0 || $closest[0] == $selected[0] || $.contains($closest, $selected);
        }).each(function () {
            result.push(this);
        });
    });
    return result;
}

http://jsfiddle.net/JCA23/

选择那些不在掩码父级中的元素或者它们最接近的掩码父级与root相同或者它们最接近的掩码父级是root的父级。

答案 3 :(得分:2)

根据我的理解,我会绑定到.controls元素并允许事件冒泡到它们。从那里,如果需要,您可以获得最接近的.Interface以获得父级。通过这种方式,您可以在兔子洞的下方添加多个处理程序。

虽然我看到你提到它,但我从未见过它。

//Attach the event to the controls to minimize amount of binded events
$('.controls').on('click mouseenter mouseleave', function (event) { 
    var target = $(event.target),
        targetInterface = target.closest('.Interface'),
        role = target.data('role');

    if (event.type == 'click') {
        if (role) {
            switch (role) {
                case 'ReplyButton':
                    console.log('Reply clicked');
                    break;
                case 'VoteUp':
                    console.log('Up vote clicked');
                    break;
                case 'VoteDown':
                    console.log('Down vote clicked');
                    break;
                default:
                    break;
            }
        }
    }
});

这是显示我的意思的fiddle。我确实删除了你的js,转而采用简化的显示方式。

看起来我的解决方案似乎过于简化了......


更新2

所以here是一个小提琴,它定义了一些有助于实现你所寻找目标的常用功能......我想。 getInterfaces提供了一个简化的函数来查找接口及其控件,假设所有接口始终都有控件。

虽然可能会出现边缘情况。我也觉得如果你已经冒险走上这条道路并且我只是没有看到/理解,我需要道歉!


更新3

好的,好的。我想我明白你想要什么。您希望获得唯一接口,并拥有一组属于它的控件,这些控件现在才有意义。

this fiddle为例,我们同时选择.Interface.Interface .controls

var interfacesAndControls = $('.Interface, .Interface .controls');

通过这种方式,我们可以得到一个简洁的接口集合和属于它们的控件,以便它们出现在DOM中。有了这个,我们可以遍历集合并检查当前元素是否与.Interface相关联。我们还可以保留对我们为其创建的当前接口对象的引用,以便稍后添加控件。

if (el.hasClass('Interface')){
    currentInterface = new app.Interface(el, [], eventCallback);

    interfaces.push(currentInterface);

    //We don't need to do anything further with the interface
    return;
};

现在,当我们没有与该元素关联的.Interface类时,我们得到了控件。因此,让我们首先修改我们的Interface对象,以支持在控件添加到集合时向控件添加控件和绑定事件。

//The init function was removed and the call to it
self.addControls = function(el){
    //Use the mouseover and mouseout events so event bubbling occurs
    el.on('click mouseover mouseout', self.eventCallback)
    self.controls.push(el);
}

现在我们要做的就是将控件添加到当前的接口控件中。

currentInterface.addControls(el);

毕竟,你应该得到一个包含3个对象(接口)的数组,每个对象有一个包含2个控件的数组。

希望,有你想要的一切!

答案 4 :(得分:2)

我认为这是最接近findExclude可以优化的:

$.fn.findExclude = function (Selector, Mask) {
    var result = $([]);
    $(this).each(function (Idx, Elem) {
        $(Elem).find(Selector).each(function (Idx2, Elem2) {
            if ($(Elem2).closest(Mask)[0] == Elem) {
                result =  result.add(Elem2);
            }
        });
    });
    return result;
}

另外,请查看其fiddle添加的日志,其中包含已用完的时间(以毫秒为单位)。

我看到你对表演很担心。所以,我已经运行了一些测试,这个实现不会超过2毫秒,而你的实现(作为你发布的答案)有时需要大约4~7毫秒。

答案 5 :(得分:0)

为什么不把问题颠倒过来?

选择所有$(。target)元素,然后如果他们的。$ parents(.group)为空,则将其从进一步处理中丢弃,这样可以使声音像:

$('.target').each(function(){
    if (! $(this).parents('.group').length){
        //the jqueryElem is empy, do or do not
    } else {
        //not empty do what you wanted to do
    }
});

请注意,不要回答标题,但实际上会在选择器A&#34;的结果中给出&#34;选择器B.

答案 6 :(得分:0)

如果你的.interface类有某种标识符,这似乎相当容易。 由于其他原因,您已经拥有这样的标识符,或者选择包含一个标识符。

http://jsfiddle.net/Dc4dz/

<div class="interface" name="a">
    <div class="control">control</div>
    <div class="branch">
        <div class="control">control</div>
        <div class="interface">
            <div class="branch">
                <div class="control">control</div>
            </div>
        </div>
    </div>
    <div class="interface" name="c">
        <div class="branch">
            <div class="control">control</div>
        </div>
    </div> </div>

$( ".interface[name=c] .control:not(.interface[name=c] .interface .control)" ).css( "background-color", "red" );
$( ".interface[name=a] .control:not(.interface[name=a] .interface .control)" ).css( "background-color", "green" );

编辑:现在我想知道你是否从错误的角度解决了这个问题。

  

所以我想尝试$(&#39; .Interface&#39;)。每个(绑定到任何.controls不   在更深层次。接口)

http://jsfiddle.net/Dc4dz/1/

$(".interface").on("click", ".control", function (event) {
    alert($(this).text());
    event.stopPropagation();
});

该事件将在.control上触发;然后它将冒泡到它的.closest(&#34; .interface&#34;),在那里它将被处理并进一步传播停止。那不是你所描述的吗?