Actionscript3 offre, comme beaucoup de langage, une structure de boucle sur les propriétés dynamiques d'un objet : for each.. in (à ne pas confondre avec forEach qui est une propriété de la classe Array).

La documentation est assez claire pour l'utilisation générale : Procède à une itération sur les éléments d'une collection et exécute statement pour chaque élément.

Mais que se passe-t-il si on modifie le tableau pendant l'itération ? La doc reste muette sur ce sujet… alors il faut tester.

Cas générique :

var Test:Array = new Array(1, 2, 3, 4, 5);
for each(var Item:int in Test)
	trace(Item);

Qui nous donne bien :

1
2
3
4
5

Première information : for each semble conserver l'ordre des données. Mais c'est à confirmer plus tard…
Au moins aussi intéressant, seules les propriétés dynamiques sont affichées : sinon on verrait défiler toutes les propriétés de la classe Array (length par exemple). Mais ça, c'était clairement explicité dans la doc.

Ajout

Deuxième test : et si on modifie le tableau dans le for each, par exemple en ajoutant un élément ?

Distinguons deux cas : l'ajout à la fin (méthode push()) et l'ajout au début (unshift()).

Ajout par la fin

for each(var Item:int in Test)
{
	trace(Item);

	if (Item == 5)
		Test.push(6,7,8);
}
1
2
3
4
5
6
7
8

L'ajout par la fin marche donc correctement.
Et l'ajout par le début ?

Ajout par le début

var Test:Array = new Array(1, 2, 3, 4, 5);
for each(var Item:int in Test)
{
	trace(Item);

	if (Item == 5)
		Test.unshift(6,7,8);
}

Cette fois les résultats sont plus intéressants :

1
2
3
4
5
3
4
5
3
4
5
3
4
5
3
4
5
3
4
5
…

Que voit-on ? Les 5 premiers éléments se tracent correctement, puis on effectue le unshift(). Et là, plutôt que d'afficher les nouveaux éléments, flash recule de trois éléments et recommence à afficher 3-4-5, 5 rajoute à nouveau des éléments, et ainsi de suite. Un code à priori correct s'avère donc faux, et pire fait crasher l'animation avec une boucle infinie !
Comment le comprendre ? Flash doit garder une liste des propriétés à itérer. Ici, il s'agit d'un tableau, donc les propriétés sont des numéros d'index. Au démarrage, Flash s'initialise : la « plus petite » clé est 0 (les tableaux commencent à 0). Ensuite vient 1, 2, 3 et 4.
Sur le quatrième élément (qui contient la valeur 5), nous décalons les index : l'index 0, au lieu de contenir la valeur 1, contient maintenant 6, et ainsi de suite, ce qui crée les index 5, 6 et 7. Flash continue son itération : 5, 6 et 7 qui contient 5, qui décale à nouveau tout le monde de trois éléments… et ainsi de suite.

Suppression d'un élément

Comme pour l'ajout, il faut distinguer la suppression d'un élément à la fin (méthode pop()) et par le début (méthode shift()).

Suppression par la fin

var Test:Array = new Array(1, 2, 3, 4, 5);
for each(var Item:int in Test)
{
	trace(Item);
	if (Item == 2)
		Test.pop();
}
1
2
3
4

Rien à dire, comportement normal : on a supprimé le 5 qui n'est donc pas affiché.

Suppresion par le début

var Test:Array = new Array(1, 2, 3, 4, 5);
for each(var Item:int in Test)
{
	trace(Item);
	if (Item == 2)
		Test.shift();
}
1
2
4
5

C'est le même comportement que pour l'ajout : les index sont automatiquement décalés. Flash est sur l'index #1, fait glisser tout le monde (la valeur 3 passe en #1) et incrémente la propriété actuellement examinée : #2, ce qui fait que le 3 n'est jamais testé.

Changement de la variable pointée

Et si le tableau est entièrement modifié pendant l'itération ? Pour bien comprendre, je vais effectuer la modification sur la valeur 2 :

var Test:Array = new Array(1, 2, 3, 4, 5);
for each(var Item:int in Test)
{
	trace(Item);

	if (Item == 2)
		Test = new Array(6,7,8,9,10);
}

Et le trace :

1
2
3
4
5

Pourtant, on pourrait s'attendre à ceci :

1
2
6
7
8
9
10

En fait, for each..in semble faire une référence vers l'objet pointé par Test à la première itération, puis ne travailler qu'avec cette référence sans se soucier du fait que Test pointe ensuite vers une autre variable. En fait, c'est la notion cachée de pointeur qui joue ici : l'objet pointé n'est plus le même (présence de new()).

Ordre d'énumération

Enfin, que peut-on dire sur l'ordre d'énumération ? S'agit-il de l'ordre lexicographique ou de l'ordre dans lequel les valeurs sont ajoutées ?

var Test:Array = new Array();
Test[3] = 1;
Test[4] = 0;
Test[2] = 2;
Test[1] = 3;
for each(var Item:int in Test)
{
	trace(Item);
}
3
2
1
0

Bref, un affichage par index croissant.

On peut donc tirer nos conclusions et complémenter la documentation floue :

  • for each.. in procède à une itération sur l'objet Object.
  • À l'initialisation, une référence est faite vers Object, puis Object n'est plus utliisé (si on ajoute des éléments, on les ajoute sur la même variable, si on change Object for each.. in s'en fiche)
  • L'initialisation se fait sur la plus petite propriété, puis va en croissant.

Bon code !