Ефективний, стислий спосіб знайти наступного відповідного брата або сестру?


77

Дотримуючись офіційного API jQuery, чи є більш лаконічний, але не менш ефективний спосіб пошуку наступного побратима елемента, який відповідає даному селектору, крім використання nextAllз :firstпсевдокласом?

Коли я кажу офіційний API, я маю на увазі не злом внутрішніх елементів, перехід прямо до Sizzle, додавання плагіна до суміші тощо (якщо мені все одно доведеться це робити, нехай буде, але це не питання. )

Наприклад, враховуючи таку структуру:

<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>

Якщо у мене є divin this(можливо, у clickобробнику, що завгодно) і я хочу знайти наступний div-брат, що відповідає селектору "div.foo", я можу зробити це:

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

... і це працює (якщо я починаю з "П'ять", наприклад, він пропускає "Шість" і "Сім" і знаходить "Вісім" для мене), але це незграбно, і якщо я хочу відповідати першому з будь-якого з кілька селекторів, стає набагато незграбнішим. (Звичайно, це набагато коротше, ніж був би необроблений цикл DOM ...)

Я в основному хочу:

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

... де nextMatchingможна прийняти весь спектр селекторів. Мене завжди дивує, що next(selector)цього не робить, але цього не робить, і документація чітко розуміє, що робить, тому ...

Я завжди можу написати це і додати, хоча, якщо я це роблю і дотримуюся опублікованого API, все стає досить неефективним. Наприклад, наївний nextцикл:

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

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

... помітно повільніше, ніж nextAll("selector:first"). І це не дивно, nextAllможна передати все це Sizzle, і Sizzle було ретельно оптимізовано. Наївний цикл вище створює та викидає всілякі тимчасові об’єкти, і йому доводиться щоразу перепроводити селектор, не дивно, що це повільно.

І звичайно, я не можу просто кинути :firstна кінець:

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

... тому що, хоча це буде працювати з простими селекторами, такими як "div.foo", воно не вдасться з "будь-яким із декількох" параметрів, про які я вже говорив, наприклад, скажімо "div.foo, div.bar".

Редагувати : Вибачте, мав би сказати: нарешті, я міг просто використовувати, .nextAll()а потім використовувати .first()результат, але тоді jQuery доведеться відвідати всіх братів і сестер, щоб просто знайти першого. Я хотів би, щоб він зупинився, коли отримає матч, а не переглядав повний список, аби він міг відкинути всі результати, крім першого. (Хоча, здається, це відбувається дуже швидко; див. Останній приклад у порівнянні швидкості, зв’язаному раніше.)

Заздалегідь спасибі.


ви коли-небудь знаходили швидший спосіб зробити це, ніж .nextAll().first()?
Ennui

1
@Ennui: Ні, це, здається, найкращий спосіб.
TJ Crowder

Відповіді:


95

Ви можете передати декілька селекторів результату .nextAll()та використовувати .first()його, наприклад:

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

Редагувати: Просто для порівняння, тут він доданий до тестового набору: http://jsperf.com/jquery-next-loop-vs-nextall-first/2 Цей підхід набагато швидший, оскільки це проста комбінація передачі .nextAll()селектор від машинного коду , коли це можливо (кожен поточний браузера) і тільки взявши перший з результуючого набору .... шляху швидше , ніж будь-який цикл можна зробити чисто в JavaScript.


1
Вибачте, я повинен був зазначити це у своєму запитанні (і зараз маю): я не хочу зайво відвідувати всіх наступних братів і сестер, аби лише знайти першого.
TJ Crowder

@TJ - Тоді відповідь - ні, немає кращого вбудованого способу зробити це. Я просив про .next(selector, true)подібне або подібне перевантаження для того, про що ви говорите, об’їжджаючи, поки його не знайдуть, а не повернувшись, якщо його знайдуть, але схоже, що цього обговорення вже немає в .next()документації: api.jquery.com/next
Нік Кравер

@TJ - Я додав тестовий приклад до набору, ви побачите, що це набагато швидше у кожному сучасному браузері з причин, доданих до відповіді :)
Нік Кравер

2
що швидше '.first ()' або '.eq (0)'?
Mark Schultheiss

5
@Mark - .eq(0)був би найшвидшим, оскільки те, що .first()викликає , але різниця там у порівнянні з усім іншим нескінченно мала , тому я б вибрав більш читабельний код у цьому випадку. З огляду на це, мій приклад не є плагіном ... якщо люди не використовують його безпосередньо, неодмінно йдіть .eq(0).
Нік Кравер

9

Як щодо використання firstметоду:

jQuery.fn.nextMatching = function(selector) {
    return this.nextAll(selector).first();
}

Вибачте, я повинен був зазначити це у своєму запитанні (і зараз маю): я не хочу зайво відвідувати всіх наступних братів і сестер, аби лише знайти першого.
TJ Crowder

1

Редагувати, оновити

Використання засобу вибору наступних братів і сестер (“попередні брати та сестри”)

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

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


Примітка, ще не доданий до тесту порівняння та не випробуваний на ньому. Непевно, чи насправді ефективніше .nextAll()реалізації. Шматок намагається проаналізувати аргумент рядка селектора з кількома розділеними комами selector. Повертає .first()елемент одиночних або розділених комами селекторів, наданих як аргумент, або thisелемент, якщо selectorаргумент не надано .nextMatchTest(). З'являються, щоб повернути ті самі результати на хромі 37, тобто 11

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


+1 Це було б чудово, якби це працювало на IE. Але, на жаль, це не так, IE не вдається знайти елемент (я думаю, будь-яке обхідне рішення, яке jQuery використовує для невкоріненого ~комбінатора, не працює на IE). (BTW, $(selector, context)ймовірно, в якийсь момент зникне, і все $(context).find(selector)одно просто перетворюється на , тому використання цього буде трохи швидше.) Jsperf.com/jquery-next-loop-vs-nextall-first/11 Але, на жаль, якщо навіть сучасний IE з цим не справляється ...
TJ Crowder,

@ гість: У цей момент нам доводиться хвилюватися, чи це спрацьовувало у всіх випадках, і в будь-якому випадку selectorніколи не було надійним, було припинено в 1.7 та видалено в 1.9 (хоча це все ще є, це частина внутрішніх органів, лише для підтримки liveплагіна міграції).
TJ Crowder,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.