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

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

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

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

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

falsarella : 我的解决方案有很好的改进,findExclude(),在许多情况下都是最好的

Zbyszek :一种类似于falsarella的基于filter的解决方案,效率也很高

贾斯汀 :一个完全不同但可管理和function性的解决方案来解决问题

这些都有其独特的优点,值得一提。

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

选择路径图 编辑:用我网站上的一些替换原始代码示例

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

与代码交互并在JS Fiddle中查看它,感谢Dave A,这里在查看JavaScript控制台时单击按钮,可以看到控件类在每个级别的.Interface嵌套中被绑定了一个额外的时间。

Visual A,论坛布局结构:

  
    • ... condensed ...
    • ... condensed ...
    • ... condensed ...
    • ... condensed ...
    • ... condensed ...
  • 在每个

  • ,可以有相同结构的任意数量的重复(每个组是消息的线程)和/或更深的嵌套,例如..

      
    • ... condensed ...
      • ... condensed ...
      • ... condensed ...
  • 在每个

  • ...
  • 有一个由另一个团队决定的任意位置,其中可能出现class="controls"并且应该绑定一个事件监听器。 虽然这些包含消息,但其他组件任意构造它们的标记,但在.Interface总是有.Interface ,它们被收集到.InterfaceGroup 。内部内容(对于论坛post)的复杂度降低版本在下面供参考。

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

     
      • ...condensed, nothing clickable...
      • Hi there!

      • TEST Message here
      • ... condensed ...
      • ... condensed ...

    我们只能绑定到Interface类中的控件, instance可能存在也可能不存在但是接口会存在。 事件冒泡到.controls元素,并引用包含它们的.Interface

    所以我试图$('.Interface').each( bind to any .controls not inside a deeper .Interface )

    这是棘手的部分,因为

    • .Interface .controls将在.each()中多次选择相同的.control
    • .not(’。Interface .Interface .controls’)取消任何更深嵌套中的控件

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

    我一直在考虑,也许,使用没有选择器的孩子可以工作,并且可以做同样的事情,如发现在引擎盖下,但我不太确定它实际上是或不会导致可怕的性能。 然而,一个有效的答案是递增的。儿童是可以接受的。

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

    减少JavaScript部分:

     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); }); } ); 

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

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

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

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

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

    然而,在完成项目的过程中,我最终完成了相当多的工作,并提出了一个相当干净的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; } 

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

     $.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的父级。

    我认为这是最接近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; } 

    此外,请查看其添加的日志,其中包含以毫秒为单位的经过时间。

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

    根据我的理解,我会绑定.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; } } } }); 

    这是一个显示我的意思的小提琴 。 我确实删除了你的js,转而使用简化的显示。

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


    更新2

    所以这里有一个小提琴,定义了一些有助于实现你所寻找的常见function……我想。 假设所有接口始终具有控件, getInterfaces提供了一个简化的函数来查找接口及其控件。

    可能会出现边缘情况。 如果你已经冒险沿着这条道路前进,而我只是没有看到/理解,我也觉得我需要道歉!


    更新3

    好的好的。 我想我明白你想要什么。 您希望获得独特的接口并拥有属于它的控件集合,这些控制现在有意义。

    使用这个小提琴作为示例,我们选择.Interface.Interface .controls

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

    通过这种方式,我们可以得到一个简洁的接口集合和属于它们的控件,以便它们出现在DOM中。 有了这个,我们可以遍历集合并检查当前元素是否具有.Interface关联的.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个控件的数组。

    希望,有你想要的一切!

    如果我理解你:

    更好地了解您的需求并应用您需要的特定类,我认为这是语法可行的:

     var targetsOfTopGroups = $('.InterfaceGroup .Interface:not(.Interface .Interface):not(.Interface .InterfaceGroup)') 

    这个小提琴试图重现你的场景。 随意玩它。


    我想我发现了这个问题。 您没有在not选择器中包含按钮

    我改变了绑定

      var Controls = $('.InterfaceGroup .Interface :button:not(.Interface .Interface :button):not(.Interface .InterfaceGroup :button)'); 

    小提琴

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

    选择所有$(。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 } }); 

    请注意,不回答标题,但字面上给你“选择器B,在选择器A的结果内”

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

    http://jsfiddle.net/Dc4dz/

     
    control
    control
    control
    control
    $( ".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" );

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

    所以我试图$(’。接口’)。每个(绑定到任何.controls不在更深的.Interface内)

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

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

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