Einen eigenen Iterator schreiben
Problem
Sie haben in einem jQuery-Objekt mehrere Elemente selektiert und müssen diese durchlaufen, wobei zwischen jeder Iteration eine kurze Pause liegen soll. Damit kann man zum Beispiel die Elemente nacheinander anzeigen:
<span class="reveal">Achtung! </span> <span class="reveal">Auf die Plätze! </span> <span class="reveal">Fertig! </span> <span class="reveal">Los!</span>
Sie haben each()
ausprobiert,
aber damit werden natürlich alle Elemente auf einmal behandelt:
$('.reveal').each( function() { $(this).show(); }); // Das war nicht besser als diese einfachere Version: $('.reveal').show();
Lösung
Schreiben Sie einen eigenen Iterator, der
setTimeout()
nutzt, um die Callbacks ein wenig zu
verzögern:
// Iterieren über ein Array (meist ein jQuery-Objekt, kann aber // jedes Array sein) und Aufruf einer Callback-Funktion für jedes // Element mit einer Zeitverzögerung zwischen jedem Aufruf. // Der Callback erhält die gleichen Argumente wie ein normaler // jQuery.each()-Callback. jQuery.slowEach = function( array, interval, callback ) { if( ! array.length ) return; var i = 0; next(); function next() { if( callback.call( array[i], i, array[i] ) !== false ) if( ++i < array.length ) setTimeout( next, interval ); } return array; }; // Iterieren über "this" (ein jQuery-Objekt) und Aufrufen einer Callback- // Funktion für jedes Element mit einer Zeitverzögerung zwischen // jedem Aufruf. // Der Callback erhält die gleichen Argumente wie ein normaler // jQuery(...).each()-Callback. jQuery.fn.slowEach = function( interval, callback ) { return jQuery.slowEach( this, interval, callback ); };
Dann ändern Sie einfach Ihren Code für
.each()
in .slowEach()
und ergänzen den Verzögerungswert:
// Alle halbe Sekunde ein Element anzeigen $('.reveal').slowEach( 500, function() { $(this).show(); });
Diskussion
Die jQuery-Methode .each()
ist kein Hexenwerk. Reduzieren wir den
Code der Implementierung in Version 1.3.2 auf den häufigsten
Anwendungsfall (das Iterieren über ein jQuery-Objekt), bleibt eine
recht einfache Schleife:
jQuery.each = function( object, callback ) { var value, i = 0, length = object.length; for( value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} return object; };
Dies ließe sich auch etwas besser lesbar schreiben:
jQuery.each = function( object, callback ) { for( var i = 0, length = object.length; i < length; ++i ) { var value = object[i]; if( callback.call( value, i, value ) === false ) break; } return object; };
Wir können ähnliche Funktionen schreiben, um
über Arrays oder jQuery-Objekte auf anderen, nützlichen Wegen zu
iterieren. Ein einfacheres Beispiel als .slowEach()
ist eine Methode, die rückwärts über
ein jQuery-Objekt iteriert:
// Rückwärts über ein Array oder jQuery-Objekt iterieren jQuery.reverseEach = function( object, callback ) { for( var value, i = object.length; --i >= 0; ) { var value = object[i]; console.log( i, value ); if( callback.call( value, i, value ) === false ) break; } }; // Rückwärts über "this" (ein jQuery-Objekt) iterieren jQuery.fn.reverseEach = function( callback ) { jQuery.reverseEach( this, callback ); return this; };
Dabei wird nicht versucht, alle Fälle zu
behandeln, um die sich .each()
kümmert, sondern nur die einfachsten Varianten für typischen
jQuery-Code.
Interessanterweise muss ein eigener Iterator
gar nicht unbedingt eine Schleife nutzen. .reverseEach()
und die Standard-Version .each()
verwenden beide ganz gewöhnliche
Schleifen, doch in .slowEach()
gibt es
gar keine. Warum ist das so und wie iteriert man durch die
Elemente, ohne eine Schleife zu nutzen?
JavaScript hat in einem Web-Browser keine
sleep()
-Funktion, wie es sie in vielen
Programmiersprachen gibt. Man kann Skripten also nicht einfach so
pausieren lassen:
doSomething(); sleep( 1000 ); doSomethingLater();
Stattdessen – wie bei allen asynchronen
Aktivitäten in JavaScript – erhält die Funktion setTimeout()
einen Callback, der dann aufgerufen wird, wenn die
Wartezeit vorüber ist. Die Methode .slowEach()
erhöht die »Schleifen«-Variable
i
im Callback von setTimeout()
. Dazu wird ein Closure genutzt, um
den Wert der Variablen zwischen zwei »Iterationen« zu sichern. (In
Was ist an $(this) falsch? sind Closures genauer
beschrieben.)
Wie .each()
arbeitet .slowEach()
direkt auf dem jQuery-Objekt oder dem
Array, das Sie mitgeben. Daher beeinflussen alle Änderungen, die
Sie an diesem Array vor dem Ende des Iterierens vornehmen, auch die
Iteration. Anders als .each()
ist
.slowEach()
asynchron (die Aufrufe der
Callback-Funktion erfolgen, nachdem .slowEach()
zurückkehrt). Wenn Sie also das
jQuery-Objekt oder seine Elemente ändern, nachdem .slowEach()
zurückkehrt, aber bevor alle Callbacks
abgearbeitet sind, kann das auch die Iteration beeinflussen.