ActionScript : Les secrets des prototypes
Par -Alexandre LEGOUT aka LAlex- le lundi, mars 1 2004, 09:18 - Articles - Lien permanent
Les prototypes sont la base de la POO avec Flash, même en AS2. C'est
pourquoi j'ai considéré qu'il pouvait être sympa de faire une explication
détaillée de leur fonctionnement, dans un didacticiel rédigé pour MediaBox. En fait, l'idée de ce tutoriel m'est
venue il y a un petit moment deja, quand dans un éclair, j'ai fini par
comprendre pourquoi la méthode d'héritage préconisée par Macromedia faisait
effectivement de l'héritage .... ![]()
Auteur : LAlex
Mail : lalex@flash-forum.net
URL : http://www.flash-forum.net
Date de création : 26/02/2004
Version : Flash 5 / MX / MX 2004
Pré requis
Connaître les principes de la POO
Pour appliquer dans Flash MX 2004 : De ActionScript 1 à ActionScript
2
Introduction
Le programmation orientée objet sous Flash est dite "prototype based", soit en français "basée sur les prototypes". Ce type de programmation est opposée à la programmation dite "class based", utilisée par la plupart des langages objets célèbres, tels que le C/C++/C# ou Java. Bien qu'essayant de simuler la même syntaxe que ces derniers langages, l'ActionScript 2 reste un langage de prototypes, transformé en ActionScript 1 lors de la compilation. C'est pourquoi il est également important de connaître les rouages de cette mécanique même si l'on utilise la dernière mouture de Flash.
Bien que limitant quelques possibilités très avancées de la POO, les prototypes sont également un outil fantastique, permettant une programmation plus souple, et une compréhension plus facile du fonctionnement interne de votre code. Nous allons ici faire le tour des spécificités de la programmation à base de prototypes, et plus particulièrement celle de Flash.
1. D'ou vient le prototype ?
Nous partirons du constat que toute fonction à un prototype, qui est créé en même temps que la fonction. Le prototype est un objet, contenant des propriétés et des méthodes. En fait, il n'est pas nécessaire d'en savoir plus sur ce point. D'ailleurs, l'aide de Flash nous donne peu d'informations sur ce qu'est le prototype, et se contente de dire Dans une fonction constructeur ActionScript 1, la propriété prototype fait référence à un objet qui est le prototype de la classe construite.Nous parlons ici de fonction constructeur, c'est à dire qui va servir à instancier un nouvel objet. Or, toute fonction peut être utilisée comme constructeur. ActionScript 2 fonctionne de la même manière, et les équivalences avec AS1 sont décrites dans le tutorial "D'ActionScript 1 à ActionScript 2".
Lorsque l'on crée une fonction, on peut considérer que Flash crée automatiquement un prototype associé, de type Object :
// Création d'une fonction
myFunction = function() {
trace("myFunction a été appellée");
}
// Instruction implicite
// myFunction.prototype = {}
2.
L'opérateur new() Elément de base de la POO, l'opérateur
new() sert à créer un nouvel objet à partir d'une fonction constructeur. Son
fonctionnement consiste à attribuer le prototype de la fonction constructeur à
un objet nouvellement crée. Examinons les différentes étapes de la création de
cet objet. - L'opérateur new() crée un nouvel objet - Il initialise la
propriété système "__proto__" de ce nouvel objet avec le prototype de la
fonction constructeur - Il initialise la propriété système "__constructor__" de
ce nouvel objet avec la fonction constructeur - Il exécute la fonction sur
l'objet nouvellement crée Nous pouvons reproduire le fonctionnement de new() de
la manière suivante :
// Déclaration du constructeur
function MaClasse(arg) {
trace("Instance de MaClasse créée avec le paramètre : '" + arg + "'");
}
// Instruction new "classique"
var monInstance = new MaClasse("Argument");
// Même instruction décomposée
// On crée un nouvel objet
var monInstance = new Object();
// On lui attribue le prototype de la fonction
monInstance.__proto__ = MaClasse.prototype;
// On lui attribue le constructeur
monInstance.__constructor__ = MaClasse;
// On applique la fonction constructeur à l'objet
MaClasse.call(monInstance, "Argument");
3.
__proto__ et la chaîne de prototypesNous avons vu précédemment que la construction d'un objet consiste à mettre un prototype dans sa propriété __proto__. Il s'agit maintenant de comprendre pourquoi l'on peut accéder aux méthodes et propriétés d'un objet, alors qu'elles sont créées dans le prototype. Nous allons prendre l'exemple d'un appel de méthode. En fait, lorsque l'on accède à la propriété d'un objet, Flash va chercher tout d'abord si cette méthode existe dans l'objet lui-même. Si c'est le cas, c'est cette méthode qui va être utilisée. Dans le cas contraire, Flash va rechercher dans la propriété __proto__ de l'objet si la méthode est présente, et l'utiliser s'il la trouve. S'il ne la trouve toujours pas, étant donné que __proto__ est un objet, il contient lui-même une propriété __proto__, dans laquelle Flash va continuer sa recherche, et ainsi de suite, jusqu'à ne plus avoir de __proto__ dans lequel rechercher. Voici une illustration du fonctionnement :
// Classe "maison"
function MaClasse (arg) {
trace("Instance créée avec l'argument '" + arg + "'");
}
// Ajout d'une méthode dans le prototype
MaClasse.prototype.innerDoIt = function(arg) {
trace("innerDoIt exécuté");
}
// Création d'une instance
monInstance = new MaClasse("Argument");
// Création d'une fonction dans l'instance
monInstance.doIt = function() {
trace("doIt exécuté");
}
// Appel des différentes méthodes
monInstance.doIt();
monInstance.innerDoIt();
trace(monInstance.toString());
Instance créée avec l'argument 'Argument' doIt exécuté innerDoIt exécuté
[object Object] Ce code a en fait exécuté les méthodes suivantes : -
Constructeur - monInstance.doIt(); - monInstance.__proto__.innerDoIt(); -
monInstance.__proto__.__proto__.toString() (méthode toString() de Object) Cette
succession de recherche s'appelle "parcourir la chaîne de prototypes". La fin
du parcours se fait lorsque la méthode est trouvée, ou lorsque la chaîne se
termine. Nous reviendrons plus tard sur la raison pour laquelle la chaîne se
termine.4. L'héritage
Il existe deux méthodes pour implémenter l'héritage en ActionScript 1. L'instruction 'extends' d'AS2 est un mixage des deux, assez bizarre il faut bien le dire ... Pour rappel, l'héritage permet de profiter des fonctionnalités d'une classe mère dans une classe fille qui en hérite. Pour plus d'informations, rendez-vous sur le tutorial concernant les principes de la POO.
La méthode officielle de Macromedia
Elle consiste à faire du prototype de la classe fille une instance de la classe mère. Les instances de cette classe fille possèderont donc le prototype de la classe mère dans leur chaîne de prototypes.
// Classe mère<br />
Parent = function() {<br />
trace("Nouvelle instance de Parent");<br />
}<br />
// Classe fille<br />
Child = function() {<br />
trace("Nouvelle instance de Child");<br />
}<br />
// La classe fille hérite de la classe mère<br />
Child.prototype = new Parent()<br />
// Instanciation de la classe fille<br />
monInstance = new Child();
Pourquoi cette instruction permet-elle de faire de l'héritage ? Penchons nous
en détail dessus, en se servant de ce que nous avons vu ci-dessus concernant
l'instruction new() ... Si l'on utilise la notation "équivalente" de new, nous
obtiendrions :
// Classe mere<br />
Parent = function() {<br />
trace("Nouvelle instance de Parent");<br />
}<br />
// Classe fille<br />
Child = function() {<br />
trace("Nouvelle instance de Child");<br />
}<br />
// "Traduction" de Child.prototype = new Parent()<br />
Child.prototype = new Object();<br />
Child.prototype.__proto__ = Parent.prototype;<br />
Child.prototype.__constructor__ = Parent;<br />
Parent.call(Child.prototype);<br />
// "Traduction" de monInstance = new Child()<br />
monInstance = new Object();<br />
monInstance.__proto__ = Child.prototype;<br />
monInstance.__constructor__ = Child;<br />
Child.call(monInstance);
Nous avons donc dans monInstance.__proto__ = Child.prototype, et plus haut,
nous avions Child.prototype.__proto__ = Parent.prototype ... Donc, on peut en
déduire que monInstance.__proto__.__proto__ = Parent.prototype ! La méthode plus "propre".
Dans la "traduction" ci-dessus, on peut se rendre compte que toutes les lignes ne sont pas utiles. En effet, si l'on regarde la sortie générée par ce code, on peut voir Nouvelle instance de Parent Nouvelle instance de ChildEn effet, le constructeur de Parent a été appelé, alors qu'il n'est absolument pas nécessaire. Afin d'éviter cela, une méthode d'héritage préférable consiste à utiliser les lignes de traduction, mais uniquement celles qui sont nécessaires. L'instanciation de monInstance peut être refaite par l'opérateur new(), étant donné que le principe reste le même que ci dessus :
// Classe mere<br />
Parent = function() {<br />
trace("Nouvelle instance de Parent");<br />
}<br />
// Classe fille<br />
Child = function() {<br />
trace("Nouvelle instance de Child");<br />
}<br />
// Héritage<br />
// ** Child.prototype = new Object();<br />
// Cette ligne n'est pas nécessaire, étant donné que<br />
// le prototype de Child est crée automatiquement à la<br />
// création de la fonction<br />
Child.prototype.__proto__ = Parent.prototype;<br />
Child.prototype.__constructor__ = Parent;<br />
// ** Parent.call(Child.prototype);<br />
// Cette ligne n'est pas nécessaire : il est inutile<br />
// d'exécuter le constructeur de la classe mère<br />
// "Traduction" de monInstance = new Child()<br />
monInstance = new Child()
Ainsi, les fonctionnalités de l'héritage sont préservées, sans qu'aucune
instruction inutile ne soit executée. Subclass.prototype = new Object();<br />
Subclass.prototype.__proto__ = Superclass.prototype;<br />
Subclass.prototype.__constructor__ = Superclass;
Cette implémentation a fait débat, concernant le fait qu'en executant la
première ligne, à priori inutile, elle détruit le prototype actuel, détruisant
ainsi le prototype courant, et la propriété "constructor" de celui-ci ... Vous
pouvez avoir plus d'informations sur la discussion en français qui a eu lieu
sur le devblog de
LAlex.5. Pourquoi parle-t-on de "prototypes de MovieClip" ? Lorsque l'on parle de prototypes de MovieClip comme les petits utilitaires permettant de faire un mouvement élastique ou autre, il s'agit d'un abus de langage, issu des débuts de la programmation Flash durant lesquels le vocabulaire propre à la POO n'était pas des plus répandus chez les programmeur utilisant cette technologie. En réalité, pour être exact, faire un "prototype de MovieClip" signifie "rajouter une méthode au prototype de MovieClip". Etant donné que tous les clips de Flash on un __proto__ qui pointe vers MovieClip.prototype, ajouter une méthode à ce prototype revient à ajouter une méthode a chaque clip !!!
6. La cas particulier de Object
Nous parlions plus haut de parcourir la chaîne de prototypes jusqu'à ce qu'il n'y ai plus de prototype à parcourir. La question est de savoir à quel moment s'arrête cette chaîne. Le cas particulier est en fait la classe Object. Elle a pour particularité le fait que son prototype n'a pas de constructeur. Elle possède bien un prototype, comme toute fonction constructeur, c'est pourquoi il est possible d'utiliser new Object(). Par contre ce prototype n'est issu d'aucune classe, ce qui fait que le __proto__ de Object.prototype n'existe pas. C'est comme si l'instruction implicite (vue au début de ce tutoriel) à la création de la fonction "Object" n'avait pas été exécutée. En effet, cela se vérifie facilement :
// Classe interne<br />
trace("== MovieClip ==");<br />
trace(MovieClip.prototype);<br />
trace(MovieClip.prototype.__proto__);<br />
// Classe "maison"<br />
MaClasse = function() {}<br />
trace("== MaClasse ==");<br />
trace(MaClasse.prototype);<br />
trace(MaClasse.prototype.__proto__);<br />
// Classe Object<br />
trace("== Object ==");<br />
trace(Object.prototye);<br />
trace(Object.prototype.__proto__);
== MovieClip==
[object Object]
[object Object]== MaClasse ==
[object Object]
[object Object]== Object ==
[object Object]
undefined
C'est la raison pour laquelle la remontée de la chaîne de prototypes s'arrête à un moment donné, lorsqu'elle rencontre un __proto__ qui n'existe pas. Cette constatation nous amène aussi au fait que toutes les classes héritent forcément de Object, étant donné que leurs prototypes sont soit de type Object, soit héritent eux-mêmes de Object. C'est d'ailleurs pour ca que toutes les classes, personnelles ou intégrées à Flash, possèdent une méthode "toString"
Conclusion
Une fois les prototypes compris, se balader dans les chaînes de prototypes
devient beaucoup plus clair, et l'on arrive ainsi à une compréhension accrue du
fonctionnement de son code, ce qui simplifie évidemment son élaboration ainsi
que son debugage.
Les prototypes peuvent également poser des problématiques inédites, véritable
gymnastique de l'esprit. Les manipuler peut amener à des fonctionnalités très
pointues, directement au coeur du code, traitant les classes elles-mêmes comme
l'on traite des objets "normaux".
Commentaires
Voilà une synthèse remarquable !
Je refilerai le lien.
D'ailleurs à propos de lien, c'est bien dommage que le titre de la page ne corresponde pas au titre de l'article ! Il faut renommer le favoris...
Salut,
... et merci pour ce nouveau tuto
juste une petite remarque : le C n'est pas un language de POO et ne l'a jamais été ! Et puis le C# n'est pas vraiment un language qeu je qualifierais de célèbre...ce n'est qu'une mouture de Windows pour se rappprocher d'un language standard avec les fonctionnalités spécifiques à Windows (on ne les refera pas...).
Pour le reste, je fini de lire, et je dirais...mais je pars confiant : les tutos pour MediaBox sont toujours des trésors pour moi
Bye
@monsieurfil >> ce lien devrais te combler : http://www.media-box.net/tuts.php?idfl=23673
Bye
on arrete pas le progres
merci pour ce complement tres commplet d'info :cool:
zyegfryed:
euh ca serait bien de te renseigner mieux sur C# avant de sortir des arguments anti-MS primaire, ca compile autre part que sous windows et ca ne se rapproche pas d'un standard, c'est un standard a part entière.
projet mono: http://www.go-mono.com/
Shared Source CLI: http://msdn.microsoft.com/net/sscli/
standardisation ECMA et ISO/IEC: http://msdn.microsoft.com/net/ecma/
lalex:
des comments par ci par là juste pour commenter
"Bien que limitant quelques possibilités très avancées de la POO"
je ne vois pas en quoi les prototype limitent la POO, je dirais meme que c'est le contraire (voir a la fin).
"l'aide de Flash nous donne peu d'informations sur ce qu'est le prototype"
ouais mais bon ca c'est MM, la spec ECMA-262 elle donne pleins d'infos, et d'ailleurs le fait que chaque definition de fonction se voit attribuée automatiquement un prototype c'est ecrit noir sur blanc dans les specs.
"Cette succession de recherche s'appelle "parcourir la chaîne de prototypes". La fin du parcours se fait lorsque la méthode est trouvée, ou lorsque la chaîne se termine"
et le __resolve, dommage de pas en parler, ca n'existe que dans flash/actionscript et c'est tres puissant, 2 exemples concrets d'applications du __resolve:
Re: [FMX] utilisation de __resolve
t2O0nEmtDHA.1976@YAMA">news://flashcodeurs.dyndns.org/t2O0nEmtDHA.1976@YAMA
(resolver.as qui permet d'intercepter du code ;))
[FMX] essai de solution pour relier une class a un clip
DHA.1964@YAMA">news://flashcodeurs.dyndns.org/THSQgM4#DHA.1964@YAMA
(alternative aux MVC avec un ResolverProxy.as)
C'est un peu dommage que tu abordes pas l'heritage par prototype en opposition avec l'heritage de class, cad un heritage basé sur de la delegation d'objet, parce que en fait toute la difference de philosophie prototype/class est là: dans du ECMAscript based on a un acces tres tres facile au processus de delegation.
tout est tres bien expliqué en détail ici:
"Object-Based Languages"
http://sern.ucalgary.ca/courses/SENG/609.03/W98/Abadi/AbadiCh4.html
ce qui est dit en résumé:
Les langages OO n'ont pas besoin d'etre basé sur des classes.
On peut penser les objets comme un concept plus primitif que les classes: les langages object-based(prototype-based) peuvent etre aussi puissant que les langages class-based et peuvent meme emuler les classes.
On a pas besoin d'adopter les classes: les objets sont une notion plus primitive.
...
Avec la delegation, les membres d'objets sont partagés à travers les objets.
Les objets sont étendus, mais l'héritage se fait en redirigeant l'acces au membres en déléguant (ou référencant) ceux-ci vers le prototype.
La delegation implicite est la plus répandue.
Avec la delegation explicite on peut déléguer des méthodes individuellement.
La delegation explicite permet de fournir une maniere propre de faire de l'héritage multiple.
etc...
J'ai toujours l'impression que les codeurs faisant de l'OO avec du prototype-based sont en train de s'excuser, alors que ce serait plutot aux codeurs class-based de s'excuser de ne pas avoir l'esprit
suffisament ouvert pour comprendre que la POO peut se faire autrement (et voire mieux) que avec des classes.
Bref, ceux (oui je parle aussi de MM) qui poussent en avant AS2
en voullant oublier les prototype et leurs rouages n'ont RIEN compris a AS du tout.
salut zwetan,
le seul avantage(selon moi) d'utiliser les Classes VS prototype est la clarté du code. Dans le sens ou les classes ont des "interface" qui permet a n'importe quel arrivant de connaitre le fonctionnement d'une classe juste en regardant les entetes de méthodes(ce qui permet d'economiser beaucoup de temps précieux).
A+
zyegfryed >> Oups !
Oui, je le sais bien, mais j'ai un peu trop l'habitude d'associer systématiquement les langegs C, C++ et C# ... Evidemment, le C n'est pas un langage objet ! 
zwetan>> Quand je parle de fonctionnalités trés avancées, je parle par example des Interfaces. Il faut bien aouer que quand on apprend à programmer, on apprend à utiliser des class-based, donc on est parfois frustré sur certains points
Mais le prototype based vaut bien au moins la class-based, la philosophie est différente, et moins connue, mais certainement pas moins efficace ...
C'est sûr, mais ceci est un article de vulgarisation. Et le __resolve est une fonctionnalité non-documentée, donc pas forcément pérène. Ca reste trés puissant malgré tout !
Il faut bien avouer que le class-based est aussi agréable à manipuler !
Par contre, je ne suis pas trop pour faire du prototype-based avec une sytaxe de class-based : ca n'est pas clair comme démarche.
Un argument en faveur du class-based est celui de liguorien : la clarté du code ! Il faut bien avouer que c'est bien mieux organisé en AS2 : peut-être faut-il se contenter de considérer AS2 comme un outil d'organisation du code, et non pas comme un nouveau langage ... 8|
++ ^^
Pour ma part j'ai adopté à 100% l'AS2 pour sa souplesse et sa clareté... Quand on cherche réellement à pousser la structure de son code en AS1 dans tous les cas on pousse vers de l'AS2 alors bon ...
oups
... je me suis mal fait comprendre (je ne suis pas anti-MS, ni pro-Linux, on dira entre les deux...)

@zwetan : je voulais simplement souligné que pour ma part (via ma petite expérience d'informaticien que j'en ai) le C# n'était pas un language "standard" pas au sens norme, mais dans le sens ou ce n'était pas (selon moi) un language utilisé en tant que tel pour aborder des concepts de POO, tels que le sont le C++ et le Java. Pour moi, le C# est juste un language utilisé pour .NET et par .NET, un mix de C++ et de Java (pas grand chose de neuf dans les notions de POO), c'est en ce sens que je parlais de l'aspect propriétaire de Windows... Loin de moi l'idée d'ouvrir un débat sur le C#, je n'y connais pas grand chose, la seule chose que j'ai a faire est de me taire :$...
Je m'excuse encore de ma maladresse
Bye
euh, pour repondre en general:
- j'ai eut et j ai encore du code AS1 aussi bien organisé et clair que avec AS2, une convention de code ca sert justement a ca.
- si je continue a penser prototype je peux faire des choses qui ne sont pas faisables que juste avec la syntaxe AS2 en pensant class-based.
Et je parle pas de syntaxe ou autre clareté du code, je parle de possiblités techniques : embedding, delegation implicite/explicite, héritage dynamique, multi-héritage etc...
exemple:
lalex tu parlais de reutilisation de l'objet LoadVars a un moment
et bah ca:
loader.protottype.load = LoadVars.prototype.load;
c'est de la delegation implicite, c'est simple a mettre en place, c'est tres puissant, et ca permet de garder un code leger...et la syntaxe AS2 ne prevoit pas ce genre de cas!
Bref quand je dis que meme avec AS2 il faut continuer a penser prototype-based, je parle de ce genre de cas.
Je suis tout a fait d'accord avec ca ! Je pense que prédire la mort du prototype par l'AS2, c'est de l'hérésie ...
++ ^^
Très bon tuto LAlex!
Et j'ai bien aimé la réplique concernant le c et c++, ceux qui en on fait le savent bien
Moi aussi j'ai adopté l'as 2.0 à 100%, c'est structuré, clair et compréhensible par d'autres programmeur qui ne connaissent pas Flash (ex: programmeur php ou Java)
il ne faut pas sous-estimer l'AS2 zwetan! voici la traduction de ton exemple en as2 :
MonLoader.load = new LoadVars().load;
puisque l'as2 est converti en prototype, les fonctionnalitées reste les memes, c'est juste la syntaxe qui change...
A+
MonLoader.load = new LoadVars().load;La, c'est pas terrible caJe préfère donc la méthode de zwetan sur ce coup la !
ET il n'est pas possible de sous-estimer l'AS2, c'est de l'AS1 !!!
Tres bon article Lalex!
Juste pour ajouter du detail, la construction implicite du prototype se fait plutot comme ca, je pense:
myFunction.prototype = {constructor: myFunction};Sous MX et MX2004, utiliser "new Object()" crerait la propriete "__constructor__" dans le prototype ce qui n'est pas le cas. Utilise la syntaxe "{}" ne cree que que le "__proto__ = Object.prototype". Aussi, come ton tuto s'applique a Flash 5/MX/MX2004, on peut faire des distinctions entre version pour l'operateur new: Flash 5: - L'opérateur new() crée un nouvel objet - Il initialise la propriété système "__proto__" de ce nouvel objet avec le prototype de la fonction constructeur - Il initialise la propriété système "constructor" de ce nouvel objet avec la fonction constructeur - Il exécute la fonction sur l'objet nouvellement crée Flash MX: - L'opérateur new() crée un nouvel objet - Il initialise la propriété système "__proto__" de ce nouvel objet avec le prototype de la fonction constructeur - Il initialise la propriété système "constructor" de ce nouvel objet avec la fonction constructeur - Il initialise la propriété système "__constructor__" de ce nouvel objet avec la fonction constructeur - Il exécute la fonction sur l'objet nouvellement crée Flash MX2004: - L'opérateur new() crée un nouvel objet - Il initialise la propriété système "__proto__" de ce nouvel objet avec le prototype de la fonction constructeur - Il initialise la propriété système "__constructor__" de ce nouvel objet avec la fonction constructeur - Il exécute la fonction sur l'objet nouvellement crée Et toujours dans les distinctions Flash 5/M/MX2004, dans l'etablissement de la chaine d'heritage par la methode "plus propre", l'instruction "Child.prototype.__constructor__ = Parent;" n'apporte rien en Flash 5 (mais ca fait pas de mal non plus remarque ;)). Et puis un dernier chtit truc qui n'a (presque) rien a voir: pour creer un object completement vide en actionscript (sans aucune propriete, meme pas de propriete __proto__), tu peux utiliser:o = Object(); // sans new!!A+
Timoth'
j'avoue que le new n'est pas une bonne solution pour ce genre de truc, mais, puisque que l'AS2 c'est de l'AS1, rien n'empeche d'utiliser explicitement les prototypes en AS2. une sorte de proto-class based...
A+
Exactement, l'AS2 reste un langage de prototypes, et selon moi n'interdit absolument pas l'utilisation explicite des prototypes.
En fait, on peut profiter de toute la puissance de l'AS1, et des contrôles du compilo concernant les Interfaces et le typage fort !
merci msieur !
a+
J'ai remplacé les appels de méthodes apply par des call suite à une erreur qui m'a été signalée par flashcoeur !
Merci flashcoeur !
ouaips
tres tres bien cette ptite page .. du coup par curiosité g remis des protos dans mes petites classes et c assez achement pratique !!
Thx a ts
Fil des commentaires de ce billet