jquery - two - wrap div
¿Una forma eficiente y concisa de encontrar el próximo hermano compatible? (3)
¿Qué hay de usar el first
método?
jQuery.fn.nextMatching = function(selector) {
return this.nextAll(selector).first();
}
Siguiendo con la API jQuery oficial, ¿hay una forma más concisa, pero no menos eficiente, de encontrar el siguiente hermano de un elemento que coincida con un selector dado que no sea usar nextAll
con la :first
pseudoclase?
Cuando digo API oficial, me refiero a no piratear internos, ir directamente a Sizzle, agregar un complemento en la mezcla, etc. (Si termino teniendo que hacer eso, que así sea, pero esa no es la pregunta. )
Por ejemplo, dada esta estructura:
<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>
Si tengo un div
en this
(tal vez en un manejador de click
, lo que sea) y quiero encontrar el siguiente div hermano que coincida con el selector "div.foo", puedo hacer esto:
var nextFoo = $(this).nextAll("div.foo:first");
... y funciona (si comienzo con "Cinco", por ejemplo, se salta "Seis" y "Siete" y encuentra "Ocho" para mí), pero es torpe y si quiero hacer coincidir el primero de cualquiera de varios selectores, se pone mucho más ruidoso. (Por supuesto, es mucho más conciso que el bucle DOM sin procesar ...)
Básicamente quiero
var nextFoo = $(this).nextMatching("div.foo");
... donde nextMatching
puede aceptar toda la gama de selectores. Siempre me sorprende que el next(selector)
no haga esto, pero no es así, y los documentos son claros sobre lo que hace, así que ...
Siempre puedo escribirlo y agregarlo, aunque si lo hago y me apego a la API publicada, las cosas se vuelven bastante ineficientes. Por ejemplo, un next
ciclo ingenuo:
jQuery.fn.nextMatching = function(selector) {
var match;
match = this.next();
while (match.length > 0 && !match.is(selector)) {
match = match.next();
}
return match;
};
... es marcadamente más lento que nextAll("selector:first")
. Y eso no es sorprendente, nextAll
puede entregar todo a Sizzle, y Sizzle ha sido completamente optimizado. El ciclo anterior crea y arroja todo tipo de objetos temporales y tiene que volver a analizar el selector cada vez, no es de extrañar que sea lento.
Y, por supuesto, no puedo simplemente arrojar un :first
al final:
jQuery.fn.nextMatching = function(selector) {
return this.nextAll(selector + ":first"); // <== WRONG
};
... porque si bien eso funcionará con selectores simples como "div.foo", fallará con la opción "cualquiera de varios" de la que hablé, como por ejemplo "div.foo, div.bar".
Editar : Lo siento, debería haber dicho: Finalmente, podría usar .nextAll()
y luego usar .first()
en el resultado, pero luego jQuery tendrá que visitar a todos los hermanos solo para encontrar el primero. Me gustaría que se detuviera cuando se llevara a cabo una coincidencia en lugar de pasar por la lista completa solo para poder descartar todos los resultados, excepto el primero. (Aunque parece suceder muy rápido, consulte el último caso de prueba en la comparación de velocidad vinculada anteriormente).
Gracias por adelantado.
Editar, actualizado
Utilizando el siguiente selector de hermanos ("prev ~ hermanos")
jQuery.fn.nextMatching = function nextMatchTest(selector) {
return $("~ " + selector, this).first()
};
http://jsperf.com/jquery-next-loop-vs-nextall-first/10
jQuery.fn.nextMatching = function nextMatchTest(selector) {
return $("~ " + selector, this).first()
};
var nextFoo = $("div:first").nextMatchTest("div.foo");
console.log(nextFoo)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<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=''goo''>Eight</div>
Nota: Aún no se ha agregado o probado en la prueba de comparación. No estoy seguro de si realmente es más eficiente que la .nextAll()
de .nextAll()
. La pieza intenta analizar el argumento de la cadena del selector con múltiples selector
separados por comas. Devuelve el elemento .first()
de los selectores separados por coma o solos proporcionados como argumento, o this
elemento si no se proporciona ningún argumento selector
a .nextMatchTest()
. Aparecen para devolver los mismos resultados en cromo 37, ie11
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
};
$.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
};
var div = $("div:first")
, foo = div.nextMatching()
, nextFoo = div.nextMatching("div.foo")
, nextFooMultiple = div.nextMatching("div.foo, div.goo");
nextFooMultiple.css("color", "green");
nextFoo.css("color", "blue");
console.log(foo);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<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=''goo''>Eight</div>
Puede pasar un selector múltiple a .nextAll()
y usar .first()
en el resultado, como este:
var nextFoo = $(this).nextAll("div.foo, div.something, div.else").first();
Editar: solo para comparar, aquí se agrega al conjunto de pruebas: http://jsperf.com/jquery-next-loop-vs-nextall-first/2 Este enfoque es mucho más rápido porque es una combinación simple de administrar el .nextAll()
selecciona el código nativo cuando es posible (todos los navegadores actuales) y simplemente toma el primero del conjunto de resultados ... mucho más rápido que cualquier bucle que pueda hacer puramente en JavaScript.