Jak widzisz, tematem dzisiejszego wpisu są pętle JavaScript oraz dobre praktyki z nimi związane. Na temat pętli w języku JavaScript pisałem już co nieco w czasie moich przygotowań do egzaminu 70-480, a konkretnie w artykule “Sterowanie przepływem operacji w języku JavaScript”. Było to jednak dość ogólne potraktowanie tematu, a dziś chciałbym przyjrzeć się temu bliżej i pokazać jak korzystać z tego elementu języka w sposób jak najbardziej wydajny. W niniejszym poście przyjrzymy się przede wszystkim dwie pętle JavaScript: “for” oraz “for-in”, które są najczęściej stosowane.

Pętla for

Opisywana pętla służy przede wszystkim do iterowania po elementach tablic (lub obiektów przypominających tablice czyli kolekcji). Najbardziej rozpowszechnionym sposobem definiowania pętli “for” znanym też z innych języków programowania jest ten widoczny poniżej:

var testArray = [ '1', '2', '3' ];

for (var i = 0; i < testArray.length; i++){
    alert(testArray[i]);
}

W przypadku tak prostego przykładu może to nie mieć znaczenia ale dla tak napisanej pętli jak w powyższym kodzie, wartość “length” tablicy pobierana jest przy każdym przebiegu pętli. Rozważmy więc drugi, bardziej “życiowy” przykład:

var paragraphs = document.getElementsByTagName('p');

for (var i = 0; i < paragraphs.length; i++){
    alert(paragraphs[i].innerHTML);
}

Tutaj nie mamy już do czynienia ze zwykłą tablicą a z obiektem HTMLCollection. W przypadku tego rodzaju obiektów każdorazowe odwołanie do właściwości “length” skutkuje odwołanie się do struktury dokumentu DOM co jest już dużo bardziej kosztowne. W związku z tym, pierwszą dobrą praktyką jest trzymanie długości tablicy w osobnej zmiennej:

for (var i = 0, max = paragraphs.length; i < max; i++){
    alert(paragraphs[i].innerHTML);
}

Jeśli nie odpowiada nam definiowanie dwóch zmiennych na potrzeby pętli, możemy zamiast tego zastosować iterowanie odwrotne, od wartości maksymalnej do zera:

for (var i = paragraphs.length; i--;){
    alert(paragraphs[i].innerHTML);
}

Oczywiście, ten sam efekt możemy uzyskać za pomocą pętli while:

var paragraphs = document.getElementsByTagName('p'),
    i = paragraphs.length;

while (i--) {
    alert(paragraphs[i].innerHTML);
}

Należy pamiętać, że przy użyciu dwóch ostatnich przykładów, jako że iteracja odbywa się od tyłu, odwołania do elementów tablicy/kolekcji również odbywają się od tyłu. W związku z tym można je stosować w zależności od konkretnej sytuacji.

Inna sprawa, że narzędzie JSLint zaleca stosowanie zamiast “i++” oraz “i–” wyrażeń w stylu “i = i + 1” albo “i += 1”, jak dla mnie jednak, stosowanie “i++” jest na tyle rozpowszechnione wśród programistów, że każdy wie o co chodzi i nie jest to coś co wprowadzałby niepotrzebną nieczytelność.

Pętla for-in

Drugi rodzaj JavaScript’owych pętli, zwanych też wyliczeniami stosuje się do iteracji po właściwościach obiektów. Rozpocznijmy od przykładu:

var testObject = {
    first: 1,
    second: 2,
    third: 3
}

for (var prop in testObject) {
    alert(testObject[prop]);
}

Jak widać, funkcja tego rodzaju jest trochę podobna do pętli “foreach” z innych języków programowania, z tym że w każdej iteracji zwracana jest nazwa danej właściwości a nie jej wartość. Dlatego w linii ósmej, chcąc odwołać się do danej wartości, stosowana jest konstrukcja “testObject[prop]” - “prop” zawiera nazwę właściwości.

Ok, wszystko pięknie, tylko że powyższy kod został przygotowany na obsługę tak zdefiniowanego obiektu. A co jeśli ktoś gdzieś rozszerzy obiekt “Object” o dodatkową funkcję sumującą?:

Object.prototype.sum = function () {
    // sum code
};

Rozwiązaniem tego problemu (i jednocześnie ogólnie dobrą praktyką podczas pisania pętli “for-in”) jest dodanie do pętli poniższego sprawdzenia:

for (var prop in testObject) {
    if (testObject.hasOwnProperty(prop)) {
        alert(testObject[prop]);
    }
}

W opisywanym przypadku, użycie metody “hasOwnProperty” pozwoli na odfiltrowanie wszystkich właściwości (lub funkcji), które nie należą bezpośrednio do obiektu “testObject” (ponieważ zwykle nie chcemy by były one uwzględnione w tego rodzaju pętlach).

Oczywiście jest to tylko dobra praktyka i nie zawsze należy ją stosować. Na pewno istnieją przypadki gdy chcemy w pętli “for-in” pracować na wszystkich właściwościach danego obiektu. Zwykle jednak zależy nam tylko na właściwościach należących bezpośrednio do danego obiektu, a wówczas opisywany test będzie jak znalazł ;)

Pętle JavaScript - podsumowanie

To tyle na dziś jeśli chodzi o pętle w języku JavaScript. Jednocześnie zapraszam do komentowania jeśli ktoś chciałby zaproponować jeszcze jakieś usprawnienia dotyczące pętli :)