Effizienter und präziser Weg, um das nächste passende Geschwister zu finden?

77

Gibt es bei der offiziellen jQuery-API eine präzisere, aber nicht weniger effiziente Möglichkeit, das nächste Geschwister eines Elements zu finden, das mit einem bestimmten Selektor übereinstimmt, das nicht nextAllmit der :firstPseudoklasse verwendet wird?

Wenn ich offizielle API sage, meine ich, keine Interna zu hacken, direkt zu Sizzle zu gehen, ein Plug-In in den Mix einzufügen usw. (Wenn ich das am Ende tun muss, soll es so sein, aber das ist nicht die Frage. )

ZB bei dieser Struktur:

<div>One</div>
<div class='foo'>Two</div>
<div>Three</div>
<div class='foo'>Four</div>
<div>Five</div>
<div>Six</div>
<div>Seven</div>
<div class='foo'>Eight</div>

Wenn ich ein divIn habe this(vielleicht in einem clickHandler, was auch immer) und das nächste Geschwister-Div finden möchte, das dem Selektor "div.foo" entspricht, kann ich dies tun:

var nextFoo = $(this).nextAll("div.foo:first");

... und es funktioniert (wenn ich zum Beispiel mit "Fünf" beginne, überspringt es "Sechs" und "Sieben" und findet "Acht" für mich), aber es ist klobig und wenn ich mit dem ersten von einem übereinstimmen möchte Bei mehreren Selektoren wird es viel klobiger. (Zugegeben, es ist viel prägnanter als die rohe DOM-Schleife ...)

Ich möchte im Grunde:

var nextFoo = $(this).nextMatching("div.foo");

... wo nextMatchingkann die gesamte Auswahl an Selektoren akzeptiert werden. Ich bin immer überrascht, dass next(selector)dies nicht funktioniert, aber nicht, und in den Dokumenten ist klar, was es tut, also ...

Ich kann es immer schreiben und hinzufügen, obwohl die Dinge ziemlich ineffizient werden, wenn ich das tue und mich an die veröffentlichte API halte. Zum Beispiel eine naivenext Schleife:

jQuery.fn.nextMatching = function(selector) {
    var match;

    match = this.next();
    while (match.length > 0 && !match.is(selector)) {
        match = match.next();
    }
    return match;
};

... ist deutlich langsamer alsnextAll("selector:first") . Und das ist nicht überraschend, nextAllkann das Ganze an Sizzle übergeben, und Sizzle wurde gründlich optimiert. Die naive Schleife oben erzeugt und wirft alle Arten von temporären Objekten weg und muss den Selektor jedes Mal neu analysieren, keine große Überraschung, dass er langsam ist.

Und natürlich kann ich nicht einfach einen :firstam Ende werfen :

jQuery.fn.nextMatching = function(selector) {
    return this.nextAll(selector + ":first"); // <== WRONG
};

... weil dies zwar mit einfachen Selektoren wie "div.foo" funktioniert, aber mit der Option "beliebig von mehreren", über die ich gesprochen habe, wie "div.foo, div.bar", fehlschlägt.

Bearbeiten : Entschuldigung, hätte sagen sollen: Schließlich könnte ich nur das Ergebnis verwenden .nextAll()und dann verwenden .first(), aber dann muss jQuery alle Geschwister besuchen, um das erste zu finden. Ich möchte, dass es aufhört, wenn es ein Match gibt, anstatt die vollständige Liste durchzugehen, nur damit alle Ergebnisse außer dem ersten weggeworfen werden können. (Obwohl es sehr schnell zu gehen scheint ; siehe den letzten Testfall im Geschwindigkeitsvergleich zuvor verlinkten .)

Danke im Voraus.

TJ Crowder
quelle
Haben Sie jemals einen schnelleren Weg gefunden, dies zu tun als .nextAll().first()?
Ennui
1
@ Ennui: Nein, das scheint der beste Weg zu sein.
TJ Crowder

Antworten:

95

Sie können einen Pass mehrere Wähler auf .nextAll()und die Verwendung .first()auf dem Ergebnis, wie folgt aus :

var nextFoo = $(this).nextAll("div.foo, div.something, div.else").first();

Bearbeiten: Nur zum Vergleich, hier wird es der Testsuite hinzugefügt: http://jsperf.com/jquery-next-loop-vs-nextall-first/2 Dieser Ansatz ist so viel schneller, weil es eine einfache Kombination der Übergabe der .nextAll()Wählen Sie nach Möglichkeit den nativen Code aus (jeden aktuellen Browser) und nehmen Sie nur den ersten Teil der Ergebnismenge ... viel schneller als jede Schleife, die Sie nur in JavaScript ausführen können.

Nick Craver
quelle
1
Entschuldigung, ich hätte das in meiner Frage erwähnen sollen (und jetzt auch): Ich möchte nicht unnötig alle folgenden Geschwister besuchen, nur um das erste zu finden.
TJ Crowder
@TJ - Dann lautet die Antwort nein, es gibt keinen besseren Weg, dies zu tun. Ich habe eine .next(selector, true)oder eine ähnliche Überladung für das, worüber Sie sprechen, angefordert und durchlaufen, bis sie gefunden wurde, anstatt zurückzukehren, wenn sie gefunden wurde, aber es sieht so aus, als ob diese Diskussion nicht mehr in der .next()Dokumentation enthalten ist: api.jquery.com/next
Nick Craver
@TJ - Ich habe den Testfall zur Suite hinzugefügt. Sie werden sehen, dass er in jedem modernen Browser aus den Gründen, die der Antwort hinzugefügt wurden, viel schneller ist :)
Nick Craver
2
was ist schneller '.first ()' oder '.eq (0)'?
Mark Schultheiss
5
@ Mark - .eq(0)wäre die absolut schnellst, da was ist , was .first()nennt , aber der Unterschied im Vergleich zu alles andere ist unendlich klein , so dass ich für den lesbaren Code in diesem Fall gehen würde. Davon abgesehen ist mein Beispiel kein Plugin ... wenn die Leute es nicht direkt verwenden, gehen Sie auf jeden Fall mit .eq(0).
Nick Craver
9

Wie wäre es mit der firstMethode:

jQuery.fn.nextMatching = function(selector) {
    return this.nextAll(selector).first();
}
einsamer Tag
quelle
Entschuldigung, ich hätte das in meiner Frage erwähnen sollen (und jetzt auch): Ich möchte nicht unnötig alle folgenden Geschwister besuchen, nur um das erste zu finden.
TJ Crowder
1

Bearbeiten, aktualisiert

Verwenden der Auswahl für die nächsten Geschwister („vorherige Geschwister“)

jQuery.fn.nextMatching = function nextMatchTest(selector) {
     return $("~ " + selector, this).first()
};

http://jsperf.com/jquery-next-loop-vs-nextall-first/10


Hinweis: Zum Vergleichstest noch nicht hinzugefügt oder ausprobiert. Nicht sicher, ob tatsächlich effizienter als.nextAll() Implementierung. Piece versucht, ein Selector-String-Argument mit mehreren durch Kommas getrennten Zeichen zu analysieren selector. Gibt das .first()Element einzelner oder durch Kommas getrennter Selektoren zurück, die als Argument angegeben wurden, oder das thisElement, wenn kein selectorArgument angegeben wurde.nextMatchTest() . Es scheint, als würden bei Chrom 37, dh 11, dieselben Ergebnisse zurückgegeben

v2

$.fn.nextMatching = function (selector) {
    var elem = /,/.test(selector) ? selector.split(",") : selector
    , sel = this.selector
    , ret = $.isArray(elem) ? elem.map(function (el) {
        return $(sel + " ~ " + $(el).selector).first()[0]
    }) : $(sel + " ~ " + elem).first();
    return selector ? $(ret) : this
};

guest271314
quelle
+1 Das wäre genial, wenn es im IE funktionieren würde. Leider findet der IE das Element nicht (ich denke, die Problemumgehung, die jQuery für den nicht gerooteten ~Kombinator verwendet, funktioniert im IE nicht). (Übrigens,$(selector, context) wird wahrscheinlich $(context).find(selector)irgendwann verschwinden und wird sowieso nur konvertiert , so dass die direkte Verwendung etwas schneller ist.) Jsperf.com/jquery-next-loop-vs-nextall-first/11 Aber leider, wenn Selbst der moderne IE schafft es nicht ...
TJ Crowder
@ guest: An diesem Punkt müssen wir uns Sorgen machen, ob es in allen Fällen funktioniert hat und auf jeden Fall selectornie zuverlässig war und in 1.7 veraltet und in 1.9 entfernt wurde (obwohl es immer noch da ist, ist es Teil der Interna, nur dort zur Unterstützung livedes Migrations-Plugins).
TJ Crowder