与jQuery .closest()类似,但是遍历后代吗?


123

是否有类似的功能,jQuery .closest()但遍历后代并仅返回最接近的后代?

我知道有.find()功能,但是它返回所有可能的匹配,而不是最接近的匹配。

编辑:

这是最接近的定义(至少对我而言)

首先遍历所有孩子,然后遍历每个孩子。

在示例下面给出id='2'最接近.closest的后代id="find-my-closest-descendant"

<div id="find-my-closest-descendant">
    <div>
        <div class="closest" Id='1'></div>
    </div>
    <div class="closest" Id='2'></div>
</div>

请参阅JSfiddle链接


4
.find("filter here").eq(0)
暗影巫师为您耳边2012年

@ShadowWizard如果执行深度优先遍历,那么结果列表中的第零个元素可能不是“最接近”的,这取决于“最接近”的含义。
Pointy 2012年

@Pointy与:first过滤器不一样吗?
暗影巫师为您耳边

不,我不这么认为-问题是“:first”和“ .eq(0)”是指DOM顺序,但是如果“最接近”的意思是“距离最远的DOM节点”,则该对象的“孙代”将返回第一个孩子,而不是与选择器匹配的后一个“孩子”。我不确定100%当然是OP所要解决的。
Pointy

1
有一个jQuery插件到底为您做什么:plugins.jquery.com/closestDescendant
TLindig 2014年

Answers:


44

根据您对的定义closest,我编写了以下插件:

(function($) {
    $.fn.closest_descendent = function(filter) {
        var $found = $(),
            $currentSet = this; // Current place
        while ($currentSet.length) {
            $found = $currentSet.filter(filter);
            if ($found.length) break;  // At least one match: break loop
            // Get all children of the current set
            $currentSet = $currentSet.children();
        }
        return $found.first(); // Return first match of the collection
    }    
})(jQuery);

3
与jQuery .closest()函数不同,此函数也匹配被调用的元素。见我的jsfiddle。通过将其更改为 $currentSet = this.children(); // Current place它将开始于它的孩子而不是jsfiddle
allicarn 2013年

21
JQuery的最近()也可以与当前元素匹配。
大卫·霍瓦特

120

如果“近亲”后代是第一个孩子,那么您可以:

$('#foo').find(':first');

要么:

$('#foo').children().first();

或者,要查找特定元素的首次出现,可以执行以下操作:

$('#foo').find('.whatever').first();

要么:

$('#foo').find('.whatever:first');

但是,实际上,我们需要对“最近的后代”的含义有一个明确的定义。

例如

<div id="foo">
    <p>
        <span></span>
    </p>
    <span></span>
</div>

哪个<span>$('#foo').closestDescendent('span')回来?


1
感谢@ 999的详细回答,我对死者的期望是首先遍历所有孩子,然后再遍历每个孩子。在您给出的示例中,将返回第二个跨度
mamu 2012年

8
所以呼吸优先,而不是深度优先
jessehouwing 2014年

1
极好的答案。我想知道$('#foo').find('.whatever').first();是“广度优先”还是“深度优先”
Colin

1
此解决方案的一个问题是jQuery将找到所有匹配条件,然后返回第一个。尽管Sizzle引擎速度很快,但这表示不必要的额外搜索。找到第一个匹配项后,无法短路搜索并停止搜索。
icfantv

这些示例都选择深度优先的后代,而不是宽度优先的,因此不能用于解决原始问题。在我的测试中,他们都返回了第一个跨度,而不是第二个。
JrBaconCheez

18

您可以使用find:first选择:

$('#parent').find('p:first');

上一行将找到<p>的后代中的第一个元素#parent


2
我认为这是OP想要的,不需要插件。这将为每个选定元素选择选定元素后代中的第一个匹配元素。因此,如果您的原始选择有4个匹配项,则可以使用结束4个匹配项find('whatever:first')
RustyToms 2014年

3

那这种方法呢?

$('find-my-closest-descendant').find('> div');

这个“直子”选择器对我有用。


OP明确要求遍历后代的东西,而这个答案不会。这可能是一个很好的答案,但不适用于此特定问题。
jumps4fun19年

@KjetilNordin也许是因为自从我回答以来,问题已被编辑。而且,至少对我来说,最接近的后代的定义仍然不清楚。
klawipo

我绝对同意。最亲近的后代可能意味着很多事情。在这种情况下,使用父级更加容易,在每个级别上,元素始终只有一个父级。而且您对可能的编辑也是正确的。现在,我发现我的评论无论如何都无济于事,尤其是因为已经有相同类型的声明。
jumps4fun19年

1

纯JS解决方案(使用ES6)。

export function closestDescendant(root, selector) {
  const elements = [root];
  let e;
  do { e = elements.shift(); } while (!e.matches(selector) && elements.push(...e.children));
  return e.matches(selector) ? e : null;
}

考虑以下结构:

div == $ 0
├──div == $ 1
│├──股利
│├──div.findme == $ 4
│├──股利
│└──div
├──div.findme == $ 2
│├──股利
│└──div
└──div == $ 3
    ├──div
    ├──div
    └──div
closestDescendant($0, '.findme') === $2;
closestDescendant($1, '.findme') === $4;
closestDescendant($2, '.findme') === $2;
closestDescendant($3, '.findme') === null;


该解决方案有效,但是由于我不喜欢,所以我在此处发布了替代ES6解决方案(stackoverflow.com/a/55144581/2441655):1)该while表达式具有副作用。2).matches在两行而不是仅一行上使用支票。
Venryx

1

如果有人正在寻找一种纯粹的JS解决方案(使用ES6而不是jQuery),这是我使用的一种:

Element.prototype.QuerySelector_BreadthFirst = function(selector) {
    let currentLayerElements = [...this.childNodes];
    while (currentLayerElements.length) {
        let firstMatchInLayer = currentLayerElements.find(a=>a.matches && a.matches(selector));
        if (firstMatchInLayer) return firstMatchInLayer;
        currentLayerElements = currentLayerElements.reduce((acc, item)=>acc.concat([...item.childNodes]), []);
    }
    return null;
};

嘿,谢谢您引起我的注意!您说对了,回头看我自己,感觉就像是在尝试变得太聪明了,实际上很尴尬(matches两次跑步也让我有些不适)。+1 :)
ccjmne

0

我认为首先您必须定义“最接近”的含义。如果您的意思是后代节点符合您的条件(在父级链接方面距离最短),那么使用“:first”或“ .eq(0)”不一定有效:

<div id='start'>
  <div>
    <div>
      <span class='target'></span>
    </div>
  </div>
  <div>
    <span class='target'></span>
  </div>
</div>

在该示例中,第二个“ .target” <span>元素比“ start”更“近” <div>,因为它距离父级只有一跳。如果这是“最接近”的意思,则需要在过滤器函数中找到最小距离。jQuery选择器的结果列表始终按DOM顺序排列。

也许:

$.fn.closestDescendant = function(sel) {
  var rv = $();
  this.each(function() {
    var base = this, $base = $(base), $found = $base.find(sel);
    var dist = null, closest = null;
    $found.each(function() {
      var $parents = $(this).parents();
      for (var i = 0; i < $parents.length; ++i)
        if ($parents.get(i) === base) break;
      if (dist === null || i < dist) {
        dist = i;
        closest = this;
      }
    });
    rv.add(closest);
  });
  return rv;
};

这是一种hack插件,因为它构建结果对象的方式不同,但是其思想是您必须在找到的所有匹配元素中找到最短的父母路径。由于<检查,此代码偏向DOM树中的左侧元素;<=会向右偏。


0

我做了这个,没有位置选择器的实现(它们不仅仅需要matchesSelector),还可以:

演示:http : //jsfiddle.net/TL4Bq/3/

(function ($) {
    var matchesSelector = jQuery.find.matchesSelector;
    $.fn.closestDescendant = function (selector) {
        var queue, open, cur, ret = [];
        this.each(function () {
            queue = [this];
            open = [];
            while (queue.length) {
                cur = queue.shift();
                if (!cur || cur.nodeType !== 1) {
                    continue;
                }
                if (matchesSelector(cur, selector)) {
                    ret.push(cur);
                    return;
                }
                open.unshift.apply(open, $(cur).children().toArray());
                if (!queue.length) {
                    queue.unshift.apply(queue, open);
                    open = [];
                }
            }
        });
        ret = ret.length > 1 ? jQuery.unique(ret) : ret;
        return this.pushStack(ret, "closestDescendant", selector);
    };
})(jQuery);

尽管可能存在一些错误,但并没有对其进行太多测试。


0

Rob W的答案对我而言并不奏效。我将其调整为有效的。

//closest_descendent plugin
$.fn.closest_descendent = function(filter) {
    var found = [];

    //go through every matched element that is a child of the target element
    $(this).find(filter).each(function(){
        //when a match is found, add it to the list
        found.push($(this));
    });

    return found[0]; // Return first match in the list
}

对我来说,这看起来完全一样.find(filter).first(),只是速度较慢;)
Matthias Samsel

是的,你是对的。我在尚不熟悉编码时就写了这篇文章。我想我会删除这个答案
Daniel Tonon

0

如果匹配选择器,我将使用以下内容包含目标本身:

    var jTarget = $("#doo");
    var sel = '.pou';
    var jDom = jTarget.find(sel).addBack(sel).first();

标记:

<div id="doo" class="pou">
    poo
    <div class="foo">foo</div>
    <div class="pou">pooo</div>
</div>

0

即使这是一个老话题,我也无法抗拒实现我的closedChild。提供行进最少的第一个后代(呼吸优先)。一种是递归的(个人喜好),另一种是使用待办事项列表的,因此没有作为jQquery扩展的递归。

希望有人受益。

注意:递归导致堆栈溢出,我改进了另一个,现在类似于先前给出的答案。

jQuery.fn.extend( {

    closestChild_err : function( selector ) { // recursive, stack overflow when not found
        var found = this.children( selector ).first();
        if ( found.length == 0 ) {
            found = this.children().closestChild( selector ).first(); // check all children
        }
        return found;
    },

    closestChild : function( selector ) {
        var todo = this.children(); // start whith children, excluding this
        while ( todo.length > 0 ) {
            var found = todo.filter( selector );
            if ( found.length > 0 ) { // found closest: happy
                return found.first();
            } else {
                todo = todo.children();
            }
        }
        return $();
    },

});  

0

以下插件返回第n个最接近的后代。

$.fn.getNthClosestDescendants = function(n, type) {
  var closestMatches = [];
  var children = this.children();

  recursiveMatch(children);

  function recursiveMatch(children) {
    var matches = children.filter(type);

    if (
      matches.length &&
      closestMatches.length < n
    ) {
      var neededMatches = n - closestMatches.length;
      var matchesToAdd = matches.slice(0, neededMatches);
      matchesToAdd.each(function() {
        closestMatches.push(this);
      });
    }

    if (closestMatches.length < n) {
      var newChildren = children.children();
      recursiveMatch(newChildren);
    }
  }

  return closestMatches;
};

0

我一直在寻找类似的解决方案(我想要所有最接近的后代,即广度优先+所有匹配项,无论它存在于哪个级别),这就是我最终要做的事情:

var item = $('#find-my-closest-descendant');
item.find(".matching-descendant").filter(function () {
    var $this = $(this);
    return $this.parent().closest("#find-my-closest-descendant").is(item);
}).each(function () {
    // Do what you want here
});

我希望这有帮助。




By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.