Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Chaine de portée et perte de mémoire dans Flash MX

Compatible ActionScript 2. Cliquer pour en savoir plus sur les compatibilités.Par Timothée Groleau

traduction partielle de l'excellent article de Timothée Groleau : Scope Chain and Memory waste in Flash MX

La gestion de la mémoire

Vous avez probablement noté que, en ActionScript, vous ne devez jamais assigner ou libérer de la mémoire. C'est parce que ActionScript est une langage avec un niveau très élevé d'abstraction machine, il manipule la mémoire pour vous automatiquement.

Une terme très populaire concernant la gestion de la mémoire est Garbage Collector ( ramasseur de miette ). Quand vous déclarez des variables et objets, Flash leur assigne automatiquement de la mémoire. Le Garbage Collector est un procédé qui garde des traces de tout les objets qui sont toujours utilisés ou présents dans votre programme. Si il détecte qu'un objet n'est plus utilisé du tout, il le détruit et libère ainsi de la mémoire, récupérant ainsi de précieuses ressources.

Sur les Functions

var est utilisé pour déclarer des variables locales. Si vous déclarez les variables dans une fonction, les variables sont locales. Elles sont définies pour la fonction et expirent à la fin de l’appel de la fonction.

C'est un extrait de la documentation ActionScript pour l'entrée var. var est utilisé pour déclarer des variables locales dans une fonction et ces variables locales seront supprimées à la fin de l'exécution de la fonction. Dans les faits, le Garbage Collector va supprimer les variables parce qu'elles ne sont plus référencées dans l'application.

test = function ()
{
	var a = 5;
	trace ("interieur: " + a);
}
test ();
// interieur: 5
trace ("exterieur: " + a);
/*
En Sortie:
 
interieur: 5
exterieur:
*/

Je suis sur que vous êtes tous familiarisé avec cela.

Maintenant, un autre point intéressant avec les fonctions, de l'intérieur d'une fonction vous pouvez appeler une variable qui est extérieur à celle-ci :

var a = 5;
test = function ()
{
	trace (a);
}
test ();
// 5
/*
Sortie:
 
5
*/

Ouais, normal! vous avez dit ? Mais que s'est il réellement passé ? Comment Flash a accéder à la variable a alors que celle-ci était définie à l'extérieur de la fonction ? La réponse c'est que Flash a suivi la chaîne de portée liée attachée à la fonction.

La Chaîne de portée ??

Le terme chaîne de portée n'appairait qu'à un seul endroit dans le dictionnaire ActionScript, sur la page traitant de l'action with. Je recommande à tout le monde de la lire car cette page est une précieuse source d'informations. Cette page, cependant, est un peu trop complexe pour le moment, donc ralentissons.

Donc, qu'est ce que la portée ? Pour moi, la portée est l'objet que Flash inspecte lorsqu'il recherche une variable. Avec cette simple définition, la portée d'une variable est un ensemble d'objets que Flash inspecte séquentiellement lorsqu'il recherche une variable. Durant son exécution, Flash voit la chaine de portée comme une simple pile d'objets et commence la recherche par le haut de celle-ci. Si l'objet qui se trouve en haut de la pile ne contient pas la variable désirée, Flash va se déplacer vers l'objet suivant et répéter le processus jusqu'à ce que la variable soit trouvée ou que l'ensemble des objets aient été inspecté.

La chaîne de portée est uniquement orientée vers le haut pour récuperer en premier lieu, les propriétés qui sont accessibles sans définition de portée explicite. Par exemple pour le trace(a), Flash recherche a dans la chaîne de portée car a n'est pas explicitement câblée. Lorsque l'on fait trace (unObjet.a), il y a une portée explicite à rechercher pour trouver a. La portée est indiquée par la référence unObjet, donc Flash va uniquement rechercher dans l'objet unObjet la variable a et non pas dans la chaine de portée.

En Flash, le code ActionScript peut uniquement être écrit sur le scénario, qui se trouve dans un MovieClip, ( qui peut être _root ou n'importe quel clip ). Depuis la place où le code ActionScript est écrit, la chaîne de portée contient au minimum 2 éléments : l'objet courant ( qui contient le code ) et l'objet _global. Un simple test :

_global.a = 4;
var a = 5;
trace(a); // 5
delete a;
trace(a); // 4
/*
Sortie :
 
5
4
*/

Décrivons ici, ligne par ligne, ce qui se passe :

*La ligne 1 déclare une variable a dans l'objet _global. *La ligne 2 déclare une variable a dans la portée courante. *Pour la ligne 3, Flash recherche a dans la portée courante, la trouve et l'envoie en sortie ( 5 ). *La ligne 4 détruit a dans la portée courante. *Pour la ligne 5, Flash recherche a dans la portée courante, ne la trouve pas et se déplace dans l'objet suivant de la chaine de portée, recherche a, la trouve, et l'envoie en sortie (4).

La définition du terme chaîne de portée appairait clairement ici, avec l'action with car on peut s'en servir pour ajouter un objet en haut de la chaîne de portée :

_global.a = 4;
var a = 5;
obj = new Object ();
obj.a = 6;
with (obj)
{
	trace (a);
	// 6
	delete a;
	trace (a);
	// 5
	delete a;
	trace (a);
	// 4
 
}
/*
Sortie :
 
6
5
4
*/

Comme vous pouvez le voir, la même fonction est appelée 3 fois trace(a); et elle renvoie différentes valeurs à chaque fois, cela provient du fait que nous supprimons la variable a dans les portées successives, Flash a donc dut chercher de plus en plus profondément dans la chaîne de portée pour trouver une variable qui correspond a la référence a.

Objet de fonction et d'activation

Création d'une fonction

Lorsqu'une fonction est créée Flash utilise comme chaîne de portée attachée à la fonction la propre chaîne de portée de la fonction.

Une fois attachée à la fonction, la chaîne de portée de celle-ci ne peut être modifiée, aucun objet ne peut être attaché ou supprimé de à la chaîne de portée de la fonction. Par exemple :

var a = 5;
test = function ()
{
	trace (a);
	// 5
	delete a;
	trace (a);
	// undefined
 
};
obj = new Object ();
obj.a = 6;
obj.meth = test;
obj.meth ();
/*
Sortie :
 
5
undefined
*/

Bien que meth soit maintenant une méthode de l'objet obj, obj n'est PAS dans la chaîne de portée de test. Cela signifie que test n'est pas affecté par l'assignation à obj. La seule chose qui pourrait être affectée à l'intérieur de la fonction est la référence à this ( non utilisé dans l'exemple ci-dessus ), nous en apprendrons d'avantage sur la référence this un peu plus loin.

Vous pourriez penser que l'exemple précédent n'est pas juste car la fonction est d'abord créée comme test mais ce n'est pas un problème. La seule chose importante c'est le morceau de code function(){/*…*/}, c'est tellement important que même sans variable temporaire le résultat serait le même :

a = 5;
obj = new Object ();
obj.a = 6;
obj.meth = function ()
{
	trace (a);
	// 5
	delete a;
	trace (a);
	// undefined
 
};
obj.meth ();
/*
Sortie :
 
5
undefined
*/

Execution d'une fonction

Chaque fois qu'une fonction est effectuée, un nouvel objet est créé de manière transparente. Cet objet garde l'ensemble des variables locales créées avec le mot cléf var, les paramètres de la fonction et un tableau des arguments. Chaque objet est appelé objet d'activation, Ce terme, objet d'activation n'apparait qu'à un seul endroit dans le dictionnaire ActionScript, encore une fois sur la page explicative de la fonction with.

Lorsque la fonction s'exécute, la chaîne de portée de la fonction est utilisée comme chaîne de portée courante et l'objet d'activation est placé au sommet de celle-ci. Donc à l'intérieur du corps d'une fonction, lorsque le script s'exécute, la chaîne de portée est ainsi :

objet d'activation -> chaine de portée de la fonction

Dans le code suivante, si la fonction est créée sur la 1ère frame de _root, la chaîne de portée, lorsque la fonction s'exécute est ainsi :

objet d'activation -> _root -> _global

Aussi loin que la gestion de la mémoire fonction, le concept d'objet d'activation nous éclaire sur ce qui se passe exactement lorsqu'une fonction s'exécute :

- L'objet d'activation est créé. - Toutes les variables locales sont créées comme propriétés de l'objet d'activation. - Le code est exécuté avec l'objet d'activation comme objet courant. - Lorsque l'exécution est terminée, quand il n'y a plus aucun lien avec cet objet d'activation dans l'ensemble du programme, l'objet d'activation et toutes ses propriétés sont supprimées et des ressources mémoires sont libérées.

Fonctions imbriquées et perte de mémoire

Introduction

Nous arrivons enfin à la partie intéressante. Avec Flash MX, le nouveau modèle événementiel rend aisé l'assignation d'une fonction à un événement. On en vient naturellement à créer des fonctions gestionnaires pour exécuter des actions ET assigner d'autres fonctions à certains événements se produisant.

Par exemple :

resetMC = function (mc)
{
	mc._x = mc._y = 0;
	mc.onEnterFrame = function ()
	{
		this._x += 2;
		this._y += 2;
	}
}
resetMC (mc1);
resetMC (mc2);

La fonction resetMC prend comme paramètre un MovieClip, rétabli sa position à 0,0 et lui assigne une fonction onEnterFrame qui fait en sorte que le clip se déplace en diagonal en direction du coté bas droit. Cela semble raisonnable, cependant, il y a un problème.

Objet d'activation persistant

Avant que nous continuions, nous devons introduire un terme de terminologie simple. Sinon, cela va devenir compliqué d'expliquer clairement la suite. Simplement, donc, lorsque qu'une fonction est créée par une autre fonction, nous allons l'appeler fonction intérieure, la fonction créant cette fonction intérieur étant fonction extérieure.

Si vous avez compris tout ce que nous avons précédemment dit, vous savez sûrement ce qui s'est passé. Dans l'exemple précédent, chaque fois que resetMC est appelée, un objet d'activation pour la fonction exécutée est créé et ajouté à la chaîne de portée de la fonction depuis la chaîne de portée courante. Quand la ligne 3 est atteinte, la fonction intérieure est créée et est assignée comme gestionnaire onEnterFrame au clip passé en paramètre de la fonction extérieure.

Lorsque la fonction intérieure est crée la chaîne de portée courante est assignée à cette fonction comme sa propre chaîne de portée. Le fait est que l'objet d'activation de la fonction extérieure est une part de la chaîne de portée courante, cela signifie qu'une référence est gardée dans la chaîne de portée de la fonction intérieure.

Concrètement, si le code précédent était écrite sur la 1ère frame de _root, la chaîne de portée serait :

objet d'activation de //fonction extérieure// -> _root -> _global

Et lorsque que la fonction intérieure serait lancée, un peu plus tard, la chaîne de portée deviendrait :

objet d'activation de //fonction intérieure// -> objet d'activation de //fonction extérieure// -> _root -> _global

Bien, parce que l'objet d'activation de la fonction extérieure est référencée maintenant dans la chaîne de portée de la fonction intérieure et parce que la fonction intérieure est maintenant persistante, comme méthode du MovieClip, l'objet d'activation lui même devient persistant. En effet, pour le Garbage Collector, il y a maintenant une référence persistante à l'objet d'activation qui ne peut être supprimé. A cause de cela, l'objet d'activation et toutes les variables locales qu'il contient ne sera pas libéré de la mémoire.

Duplication de fonction

Il y a plusieurs implications à créer des fonctions à l'intérieur d'autres fonctions.

-Comme nous l'avons mentionné précédemment, un nouvel objet d'activation est créé à chaque exécution de la fonction. Donc, si on garde en mémoire le code précédent, imaginez que nous voulions utiliser la fonction resetMC pour assigner un onEnterFrame à 100 MovieClip : cela mènera à avoir 100 objets d'activation différents qui resterons stockés en mémoire. -Parce que la fonction intérieure est créée et assignée par la fonction extérieure, chaque clip aura une fonction intérieure différente qui lui sera affecté et cela ajoutera à chaque fois un objet d'activation supplémentaire en mémoire. Ce n'est certainement pas très efficace puisque la fonction intérieure fait exactement la même chose à chacun des clips passés en paramètres. C'est par ailleurs la raison pour laquelle la programmation orienté objet recommande de créer une méthode de classe MovieClip plutôt qu'une méthode d'occurrence de la classe MovieClip. Si les méthodes sont des méthodes d'occurrences, nous aurons des fonctions dupliquées à chaque occurrence de la classe.

Perte de mémoire, pas fuite de mémoire

Il est très important de faire une distinction entre les 2. Une fuite de mémoire est un problème rencontré lorsqu'un programme incrémente continuellement les ressources qu'il utilise, risquant potentiellement un effondrement du système.

En ce qui concerne les fonctions dans Flash, ce n'est pas une fuite de mémoire, c'est une perte de mémoire. La fonction intérieure garde une référence de l'objet d'activation de la fonction extérieure, mais ce n'est qu'une relation linéaire. Une fois que la fonction intérieure est enlevée de la mémoire (par exemple si l'objet auquel la fonction intérieure a été attachée est enlevé), alors la référence unique à l'objet d'activation est également supprimée et le Garbage Collector va ( devrait ) supprimer la fonction intérieure et l'objet d'activation de la fonction extérieure en même temps. Donc les ressources utilisées pour garder la référence de l'objet d'activation de la fonction extérieure seront récupérées et la mémoire utilisée n'atteindra pas des valeurs hors de contrôle.

Pouvons nous prouver cela ?

La persitance de l'objet d'activation

Oui, soit le morceau de code suivant :

test = function (obj)
{
	var a = 5;
	obj.meth = function ()
	{
		trace (a);
	}
}
o = new Object ();
test (o);
o.meth ();
// 5
/*
Sortie :
 
5
*/

Dans la fonction test, a est une variable locale donc après l'exécution de test, a est détruite, et l'on récupère de la mémoire. Pourtant, lorsque nous exécutons le script et appelons meth sur l'objet o passé en paramètre, la sortie est 5, ce qui signifie que a a été trouvé dans la chaîne de portée de meth et que a n'a pas été supprimé de la mémoire.

Vous pourriez penser que a a été trouvé parce qu'il y'a une programmation bas niveau qui la référence dans la fonction intérieure. Flash pourrait avoir fait “quelque chose” en interne maintenir cette référence particulière vivante. Ce serait sympa, mais vraiment, ce n'est pas le cas. L'objet d'activation de la fonction extérieure a été stocké dans la chaîne de portée de la fonction intérieure et TOUTES LES variables locales peuvent être recherchées.

En utilisant eval, on peut dynamiquement rechercher une variable dans une chaîne de portée, Jetez un oeil au code suivant :

test = function (obj)
{
	var aVariable_1 = "Hello";
	var aVariable_2 = "There";
	var aVariable_3 = "Tim";
	obj.retrieve = function (refName)
	{
		trace (eval (refName));
	}
}
o = new Object ();
test (o);
o.retrieve ("aVariable_1");
// Hello
o.retrieve ("aVariable_2");
// There
o.retrieve ("aVariable_3");
// Tim
/*
Output:
 
Hello
There
Tim
*/

Dans cet extrait de code, la fonction 'retrieve' ne contient aucune référence en dur vers une ou des variables spécifiques, pourtant en passant le nom d'une variable en argument et en employant eval, nous pouvons atteindre toutes les variables locales que nous pensions supprimées. Bien que ces variables ne sont plus utilisées dans le programme, elles sont demeurées en mémoire et gaspillent des ressources.

La duplication et la perte de mémoire

Oui, encore ! Soit le code suivant :

addFunc = function (obj)
{
	var aVariable = new Object ();
	aVariable.txt = "Hello there";
	obj.theFunc = function ()
	{
		return aVariable;
	}
}
o1 = new Object ();
o2 = new Object ();
addFunc (o1);
addFunc (o2);
trace (o1.theFunc ().txt);
// Hello there
trace (o2.theFunc ().txt);
// Hello there
trace (o1.theFunc == o2.theFunc);
// false
trace (o1.theFunc () == o2.theFunc ());
// false
/*
Sortie :
 
Hello there
Hello there
false
false
*/

La ligne 19 prouve que bien que les méthodes portées par o1 et O2 aient le même nom elles ne mettent pas en référence le même objet de fonction dans la mémoire.

La ligne 20 prouve que bien que l'objet qui est retourné par theFunc pour o1 et O2 aient la même propriété d' txt avec la même valeur, ces objets eux-mêmes sont différents, cela signifie que le string Hello there est stocké dans la mémoire deux fois, une fois pour chaque objet.

En fait chaque fois que l' addFunc est appelée pour ajouter la méthode theFunc à un objet, le string Hello there est reproduit dans la mémoire.

Que peut-on donc faire ?

À moins que vous soyez spécifiquement intéressé par les actions de la fonction interieure mentionnée en haut, la meilleure chose à faire est de créer toutes vos fonctions au même niveau et d'employer seulement des références de fonction à l'intérieur d'une fonction au lieu d'employer des fonctions interieures.

Par exemple, si nous récrivons le code ci-dessus, il pourrait être comme ceci:

theFunc = function ()
{
	return aVariable;
}
addFunc = function (obj)
{
	var aVariable = new Object ();
	aVariable.txt = "Hello there";
	obj.theFunc = theFunc;
}
o1 = new Object ();
o2 = new Object ();
addFunc (o1);
addFunc (o2);
trace (o1.theFunc ().txt);
// undefined
trace (o2.theFunc ().txt);
// undefined
trace (o1.theFunc == o2.theFunc);
// true
/*
Sortie :
 
undefined
undefined
true
*/

Comme vous pouvez voir au-dessus les trace à la ligne 17 et 18 produisent un undefined. C'est parce que, quand theFunc est appelée sur o1 et O2, Flash ne peut pas trouver la référence “aVariable” dans la chaîne de portée, qui signifie que l'objet d'activation de l' addFunc n'a pas été ajouté à la chaîne de portée des méthodes et que aVariable, variable local a été supprimé comme prévu.

En outre, la ligne 20 produit maintenant true qui signifie que l'objet de fonction pour o1 et o2 est la même et ne prend qu'une seule place dans la mémoire.

Sur haut de l'optimisation de l'espace mémoire, cette approche vous permettra d'executer plus vite votre code.

Fondamentalement, cela prend du temps pour une nouvelle fonction d'être créé dans la mémoire: du temps pour l'attribution de mémoire, du temps pour le transfert de données, etc… Ainsi quand vous employez une fonction interieure, chaque fois que vous appelez la fonction exterieure, vous prenez quelques cycles de temps processeur pour établir la fonction intérieure. Créer les fonctions au même niveau et faire un simple appel dans la fonction exterieure rendera l'execution de votre code beaucoup plus rapide.

Conclusion

Ceci conclu cet article sur la chaine de portée et la perte de memoire. Pour résumer, voici les points importants dont nous avons discuté :

- La chaine de portée est un ensemble d'objet que Flash inspecte lorsqu'il recherche une variable. - Quand une fonction est créée, la chaine de portée courante lui aie attaché. - Chaque fois qu'une fonction est executée, un nouvel objet, appelé objet d'activation, est créé pour stocker l'ensemble des variables locales et cet objet est placé au somment de la chaine de portée de cette fonction. - Avec les fonctions imbriquées, l'objet d'activation de la fonction externe est placé dans la chaine de portée de la fonction interne. - Si la fonction interne garde une référence à un objet tel qu'une méthode ou si il renvoie à une fonction externe, alors la fonction interne devient peristante en gardant l'objet d'activation de la fonction externe. Ceci provoque des pertes de mémoire. - Pour éviter la perte de mémoire, une solution simple est ne pas employer de fonctions internes mais de créer des fonctions externes à la place et d'y attacher des références seulement.

En fait, c'est tout tout à fait simple. Souvent, vous devez gaspiller des ressources.

Avant que vous arretiez cette lecture, et que vous commenciez à aller modifier tout vos scripts pour dénicher vos fonctions imbriquées, je voudrais vous dire que dans la plupart des cas, cela n'est pas si important. Dans de petits projets, cela n'influencera pas les ressources que vous exploitez et si vos scripts sont bien écrits, ne vous inquietez pas trop à ce sujet. Ce qui est mentionné ici s'applique à de gros projets, et il est toujours interressant de savoir ce qu'il se passe réélement derriere vos scripts.

Les plus grands cas de perte de mémoire se produisent quand vous traitez des textes importants et/ou beaucoup de fonctions imbriquées. Les string peuvent rapidement atteindre jusqu'à quelques centaines de caractères donc si vous utiliser de longs string comme variables locales et si ces variables deviennent persistantes, alors, vous pouvez effectivement découvrir de sérieux problèmes, si il ne s'agit que de nombres entiers ou de références, cela ne devrait pas influencer significativement vos executions de scripts.

Gardez juste en mémoire cet article lorsque que vous développez en ActionScript :)

Extras Questions/réponses

Quelle est la profondeur maximale possible pour la chaine de portée ?

Aucune idée, J'ai essayé jusqu'a 5 niveau de fonctions imbriquées et tout les objets d'activation sont restés présents :

a1 = 5;
addFunc = function (obj)
{
	var a2 = 6;
	var func = function (obj)
	{
		var a3 = 7;
		var func = function (obj)
		{
			var a4 = 8;
			var func = function (obj)
			{
				var a5 = 9;
				obj.retrieve = function (refName)
				{
					var a6 = 10;
					trace (eval (refName));
				}
			}
			func (obj);
		}
		func (obj);
	}
	func (obj);
}
o = {
};
addFunc (o);
o.retrieve ("a6");
// 10
o.retrieve ("a5");
// 9
o.retrieve ("a4");
// 8
o.retrieve ("a3");
// 7
o.retrieve ("a2");
// 6
o.retrieve ("a1");
// 5
/*
Sortie:
 
10
9
8
7
6
5
*/

Tant que nous pouvons récuperer toutes les variables, cela signifie que l'objet d'activation a été gardé pour chaque fonction imbriquée.

Donc réélement, je ne connait pas la profondeur maximale possible pour la chaine de portée, mais elle doit être probablement importante. Si votre code ressemble un peu au code ci-dessus dans un vrai projet, vous devriez penser serieusement à revoir vos habitudes de programmation.

Chaine de portée et chaine de prototype

Nous mentionions plus tôt que quand Flash recherche une variable, il recherche par la chaîne de portée. C'est vrai, mais, il y a un autre mécanisme que le flash emploie quand il recherche une variable. Ce mécanisme est la chaîne de transmission, également connue sous le nom de chaîne de prototype. Puisque cet article n'est pas une discussion sur la Programmation Orienté Objet, je n'irai pas plus profondément, mais je vous renvoie vers Robin Debreuil, qui a écrit un excellent livre en ligne sur la POO dans Flash.

En un mot, chaque objet dans Flash est une occurence d'une classe et une classe peut être héritée encore d'une autre classe ainsi de suite. Chaque classe peut avoir son propre ensemble de propriétés et de méthodes et quand vous appelez une méthode sur un objet, si la méthode n'est pas trouvé dans l'objet lui-même, Flash cherche dans la chaine de prototype si il peut trouver en amont la méthode demandée.

L'objet dans la chaîne de prototype existe par proto , quand une recherche dans la chaîne de portée est impliquée, la recherche en amont dans la chaîne de prototype est fait pour chaque objet dans la chaîne de portée.

Une petite démonstration est plus simple qu'un long discours :

a = 5;
addFunc = function(obj) {
 var __proto__ = new Object();
 __proto__.a = 6;
 obj.meth = function() {
   trace(a);
 }
}
o = {};
addFunc(o);
o.meth(); // 6
/*
Sortie :
 
6
*/

La valeur 6 n'est pas assigné à a comme une variable locale dans la fonction addFunc, pourtant elle a été sélectionnée en amont quand Flash a recherché a. Etapes par etapes, qu'est ce que Flash a fait, lors de l'execution de meth ?

En premier lieu flash a recherché une référence à la variable « a » dans l’objet d’activation de « meth » mais ne l’y a pas trouvé. Ensuite il vérifia si cet objet possédait une propriété proto . N’en trouvant aucune (aucune que l’on puisse voir tout au moins) il continua et inspecta l’objet suivant dans la chaîne de portées : l’objet d’activation de la fonction « addFunc ». « a » n’y était pas. Flash rechercha alors une propriété proto chez cet objet et il en trouva une. Il rechercha « a » dans l’objet vers lequel cette propriété pointait et l’y trouva. Il renvoya alors cette valeur.

Comment se passe l’affectation d’une variable sans référence qualifiée ?

Je ne sais pas si je n’aurais pas dû aborder ceci dès le début. La encore, c’est un comportement qui, bien que n’étant pas très compliqué, n’est pas évident. Lorsque vous affectez une valeur à une variable, par exemple : « maVar=5 », chacun des objets de la chaîne de portées est inspecté afin d’y trouver une variable portant ce nom. SAUF l’objet « _global ». Si une telle variable est trouvée dans un de ces objets (appelons le « o »), la valeur lui est alors attribuée.

Si aucune variable « maVar » n’est trouvée, elle est alors créée dans l’objet le plus bas, situé donc juste au-dessus de l’objet « _global », dans la chaîne de portées.

Lors de cette affectation, la chaîne de prototype des objets de la chaîne de portées n’est apparemment pas inspectée. Il en résulte que même si une référence « maVar » existe dans la chaîne de prototype d’un objet « o », aucune affectation ne sera faite sur cet objet à moins qu’il ne soit le dernier de la chaîne de portées, juste au-dessus de l’objet « _global ».

Pour créer une propriété de l’objet « _global » ou lui affecter une valeur, le mot-clé « _global » doit être utilisé.

Ceci est illustré par les quelques scripts ci-dessous.

o1 = {};
o2 = {};
with (o1) {
 with (o2) {
   trace("The first 'a' reference found is: " + a);
   a = 5;
 }
}
trace(o1.a); // undefined
trace(o2.a); // undefined
trace(a); // 5
 
Output:
 
The first 'a' reference found is:
undefined
undefined
5
o1 = {a:4};
o2 = {};
with (o1) {
 with (o2) {
   trace("The first 'a' reference found is: " + a);
   a = 5;
 }
}
trace(o1.a); // 5
trace(o2.a); // undefined
trace(a); // undefined
 
Output:
 
The first 'a' reference found is: 4
5
undefined
undefined
o1 = {};
o1.__proto__ = {a:4}
o2 = {};
with (o1) {
 with (o2) {
   trace("The first 'a' reference found is: " + a);
   a = 5;
 }
}
trace(o1.a); // 4
trace(o2.a); // undefined
trace(a); // 5
 
Output:
 
The first 'a' reference found is: 4
4
undefined
5

Le mot-clé « with » et la chaîne de portées.

J’aime et je déteste « with ». De par son interaction avec la chaîne de portées, il possède beaucoup de similitudes avec les fonctions imbriquées. Ce dont nous avons parlé concernant l’affectation d’une variable sans référence qualifiée, par exemple, est vrai tant pour les fonctions imbriquées que pour l’instruction « with ». Il y a néanmoins quelques différences. La plus importante d’entre elles est liée à la création de fonctions. J’ai déjà évoqué précédemment le fait que la chaîne de portées courante est assignée à une fonction lors de sa création ce qui, comme nous l’avons vu, est responsable de la persistance des objets d’activation. Si la chaîne de portées courante est assignée à une fonction lors de sa création, cela signifie que nous devrions pouvoir utiliser l’instruction « with »pour ajouter n’importe quel objet à la chaîne de portées de cette fonction. Pourtant cela ne fonctionne pas, comme l’illustre le code suivant :

 
o1 = {a:5};
o2 = {};
with (o1) {
 trace(a); // 5
 o2.aMethod = function() {
   trace(a);
 };
}
o2.aMethod(); // undefined
 
Output:
 
5
undefined

Nous avons utilisé l’instruction « with » pour placer l’objet o1 au sommet de la chaîne de portées. La ligne 4 nous montre que l’on peut, sans problèmes, récupérer la valeur de « a ». La chaîne de portées courante étant donc donnée, nous créons alors une nouvelle fonction (ligne 5) que nous assignons comme nouvelle méthode de l’objet o2. Lorsque nous appelons cette méthode par la suite, elle nous renvoie « undefined ». Cela signifie que « a »n’a pas été trouvé dans la chaîne de portée de la fonction et donc que l’objet o1 n’y a pas été ajouté.

Les objets placés manuellement dans la chaîne de portées à l’aide de l’instruction « with » ne sont pas considérés comme faisant partie de la chaîne de portées courantes lors de la création d’une fonction.

Et le « this » ?

Pour ce que j’ai pu en tester, la référence à « this » n’est pas affectée par la chaîne de portées, quelque soit sa profondeur. « this » fait toujours référence à l’objet d’où est appelée la fonction.

En d’autres termes, même si la chaîne de portées d’une fonction est invariable, un « this » placé dans cette dernière verra sa signification modifiée selon l’objet qui appelle cette fonction. Revenons à l’un de nos tout premiers exemples :

a = 5;
test = function() {
 trace(this.a);
};
 
obj = new Object();
obj.a = 6;
obj.meth = test;
 
obj2 = new Object();
obj2.a = 7;
obj2.meth = test;
 
obj.meth(); // 6
obj2.meth(); // 7
 
Output:
 
6
7

Dire de « this » qu’il pointe toujours vers l’objet d’où la fonction est appelée nous amène à une propriété très intéressante. Nous avons déjà dit que les variables locales ne sont que des propriétés de l’objet d’activation. Si, à l’aide du mot-clé « var »,nous assignons une fonction à l’objet d’activation et que nous appelons cette fonction depuis cet objet d’activation, « this » fera alors référence à l’objet d’activation lui-même !!!!! De fait, il est alors possible d’enregistrer quelque part une référence à un objet d’activation particulier (si vous en avez une quelconque utilité) ou utiliser l’opérateur d’accès aux tableaux pour récupérer ou modifier dynamiquement la valeur d’une propriété dans cet objet. (la encore, si vous y voyez le moindre intérêt).

a = 4;
test = function() {
 var a = 5;
 var getRef = function() {
   trace(this.a);
 }
 getRef(); // 5
}
test();
 
Output:
 
5

L’opérateur d’accès aux tableaux et « eval »

D’aucuns se demandent si le mot-clé « eval » est, ou non, déprécié et ce qui le différencie de l’opérateur d’accès aux tableaux. Une citation de Ralf Bokelberg nous apporte la réponse :

Connaissez vous le mantra de eval ?

"Eval n’est pas déprécié
Eval n’est pas inutile
Il est bon de l’utiliser"

Comment accéderiez-vous à des objets ciblés par une chaîne sans lui ?
obj = {subobj: {prop: 666}}
path = "obj.subobj.prop";
trace(this[path]); //undefined
trace(eval(path)); //666

La principale différence est donc que eval peut résoudre un chemin tandis que l’opérateur d’accès aux tableaux peut seulement récupérer la propriété d’un objet. Nous pouvons aussi établir la différence entre eux en considérant leur relation avec la chaîne de portées et la chaîne de prototype. En admettant que nous utilisions les deux méthodes pour récupérer une variable simple, nous pourrions dire que l’opérateur d’accès aux tableaux permet de récupérer une variable en inspectant la chaîne de prototype d’un objet donné alors qu’eval est utilisé pour récupérer la valeur d’une variable en inspectant la chaîne de portées courante. Et, comme il a été dit précédemment, l’inspection la chaîne de portées courante inclut l’inspection de la chaîne de prototype de chaque objet de la chaîne de portées courante.

Avez-vous tout compris ?

Nous sommes arrivés à la fin de notre étude. A l’aide de tout ce nous avons vu jusqu'à présent et sans ouvrir Flash, pouvez-vous prédire ce que le code suivant donnera en sortie ?

getMethod = function() {
  var setProto = function() {
    this.__proto__ = o1;
  };
  setProto();
 
  return function() {
    trace(a);
  }
}
 
_global.a = 4;
o1 = {a:5};
o2 = {a:6};
a = 7;
 
o2.theMethod = getMethod();
o2.theMethod();

4,5,6 ou 7 ?