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.

JQuery Kochbuch
titlepage.xhtml
part0000.html
part0001.html
part0002_split_000.html
part0002_split_001.html
part0002_split_002.html
part0003_split_000.html
part0003_split_001.html
part0003_split_002.html
part0003_split_003.html
part0003_split_004.html
part0003_split_005.html
part0003_split_006.html
part0003_split_007.html
part0004_split_000.html
part0004_split_001.html
part0004_split_002.html
part0004_split_003.html
part0004_split_004.html
part0004_split_005.html
part0004_split_006.html
part0004_split_007.html
part0004_split_008.html
part0004_split_009.html
part0004_split_010.html
part0004_split_011.html
part0004_split_012.html
part0004_split_013.html
part0004_split_014.html
part0004_split_015.html
part0004_split_016.html
part0004_split_017.html
part0004_split_018.html
part0005_split_000.html
part0005_split_001.html
part0005_split_002.html
part0005_split_003.html
part0005_split_004.html
part0005_split_005.html
part0005_split_006.html
part0005_split_007.html
part0005_split_008.html
part0005_split_009.html
part0005_split_010.html
part0005_split_011.html
part0005_split_012.html
part0005_split_013.html
part0006_split_000.html
part0006_split_001.html
part0006_split_002.html
part0006_split_003.html
part0006_split_004.html
part0006_split_005.html
part0006_split_006.html
part0006_split_007.html
part0006_split_008.html
part0006_split_009.html
part0006_split_010.html
part0007_split_000.html
part0007_split_001.html
part0007_split_002.html
part0007_split_003.html
part0007_split_004.html
part0007_split_005.html
part0007_split_006.html
part0007_split_007.html
part0007_split_008.html
part0007_split_009.html
part0007_split_010.html
part0007_split_011.html
part0008_split_000.html
part0008_split_001.html
part0008_split_002.html
part0008_split_003.html
part0008_split_004.html
part0008_split_005.html
part0008_split_006.html
part0008_split_007.html
part0008_split_008.html
part0008_split_009.html
part0008_split_010.html
part0008_split_011.html
part0008_split_012.html
part0008_split_013.html
part0008_split_014.html
part0008_split_015.html
part0008_split_016.html
part0008_split_017.html
part0008_split_018.html
part0008_split_019.html
part0008_split_020.html
part0008_split_021.html
part0008_split_022.html
part0009_split_000.html
part0009_split_001.html
part0009_split_002.html
part0009_split_003.html
part0009_split_004.html
part0009_split_005.html
part0009_split_006.html
part0009_split_007.html
part0009_split_008.html
part0009_split_009.html
part0009_split_010.html
part0010_split_000.html
part0010_split_001.html
part0010_split_002.html
part0010_split_003.html
part0010_split_004.html
part0010_split_005.html
part0010_split_006.html
part0010_split_007.html
part0010_split_008.html
part0010_split_009.html
part0010_split_010.html
part0010_split_011.html
part0011_split_000.html
part0011_split_001.html
part0011_split_002.html
part0011_split_003.html
part0011_split_004.html
part0011_split_005.html
part0011_split_006.html
part0011_split_007.html
part0011_split_008.html
part0011_split_009.html
part0011_split_010.html
part0011_split_011.html
part0012_split_000.html
part0012_split_001.html
part0012_split_002.html
part0012_split_003.html
part0012_split_004.html
part0012_split_005.html
part0012_split_006.html
part0012_split_007.html
part0012_split_008.html
part0013_split_000.html
part0013_split_001.html
part0013_split_002.html
part0013_split_003.html
part0013_split_004.html
part0013_split_005.html
part0013_split_006.html
part0013_split_007.html
part0013_split_008.html
part0013_split_009.html
part0013_split_010.html
part0013_split_011.html
part0013_split_012.html
part0014_split_000.html
part0014_split_001.html
part0014_split_002.html
part0014_split_003.html
part0014_split_004.html
part0014_split_005.html
part0014_split_006.html
part0014_split_007.html
part0014_split_008.html
part0014_split_009.html
part0014_split_010.html
part0014_split_011.html
part0015_split_000.html
part0015_split_001.html
part0015_split_002.html
part0015_split_003.html
part0015_split_004.html
part0015_split_005.html
part0015_split_006.html
part0015_split_007.html
part0015_split_008.html
part0015_split_009.html
part0015_split_010.html
part0016_split_000.html
part0016_split_001.html
part0016_split_002.html
part0016_split_003.html
part0016_split_004.html
part0016_split_005.html
part0016_split_006.html
part0016_split_007.html
part0016_split_008.html
part0016_split_009.html
part0017_split_000.html
part0017_split_001.html
part0017_split_002.html
part0017_split_003.html
part0017_split_004.html
part0017_split_005.html
part0017_split_006.html
part0017_split_007.html
part0017_split_008.html
part0017_split_009.html
part0017_split_010.html
part0017_split_011.html
part0018_split_000.html
part0018_split_001.html
part0018_split_002.html
part0018_split_003.html
part0018_split_004.html
part0018_split_005.html
part0018_split_006.html
part0019_split_000.html
part0019_split_001.html
part0019_split_002.html
part0019_split_003.html
part0019_split_004.html
part0019_split_005.html
part0019_split_006.html
part0019_split_007.html
part0019_split_008.html
part0019_split_009.html
part0019_split_010.html
part0020_split_000.html
part0020_split_001.html
part0020_split_002.html
part0020_split_003.html
part0020_split_004.html
part0020_split_005.html
part0020_split_006.html
part0020_split_007.html
part0020_split_008.html
part0021_split_000.html
part0021_split_001.html
part0021_split_002.html
part0021_split_003.html
part0021_split_004.html
part0021_split_005.html
part0021_split_006.html
part0021_split_007.html
part0021_split_008.html
part0021_split_009.html
part0022.html
part0023.html
part0024.html
part0025.html