Bug de constructor (Flash 5) de retour dans MX 2004
Par -Alexandre LEGOUT aka LAlex- le jeudi, octobre 16 2003, 09:56 - AS2 - Lien permanent
Pour ceux qui n'ont pas suivi les commentaires du post sur un inconvénient du typage fort, Timothee Groleau et moi nous sommes aperçu qu'un vieux bug issu de Flash 5, corrigé sur Flash MX, a refait surface dans Flash MX 2004. Il s'agit du bug de constructor lors de l'héritage.
En effet, lors de l'héritage, dans une classe fille, la propriété this.constructor pointe sur le constructeur de la classe parent, que ce soit en AS1 ou en AS2. Le problème dans Flash 5 avait été évoqué par Dave Yang : http://www.quantumwave.com/flash/inheritance.html.
Pour exemple, voici un héritage fait en AS1 au moyen de la méthode préconisée par Macromedia, avec l'instruction new :_global.Parent = function() {};
Compilé avec Flash MX, la sortie est logiquement :
_global.Child = function() {};
Child.prototype = new Parent();
Child.prototype.doIt = function() {
trace("isParent ? " + (this.constructor == Parent));
trace("isChild ? " + (this.constructor == Child));
}
var obj = new Child();
obj.doIt();isParent ? false
isChild ? true
Par contre, compilé avec Flash MX 2004, la sortie est isParent ? true
isChild ? false
De la même manière, l'AS2 utilise égallement la méthode new de Macromedia lors de sa traduction en AS1, et on a donc le même problème :class Child extends Parent {
Le seul moyen qui avait été trouvé pour rectifier ce problème avec Flash 5 était d'initialiser this.constructor à la main dans le constructeur :
// ....
function doIt() {
trace(this.constructor == Parent);
}
}
var c:Child = new Child();
c.doIt(); // true class Parent {
function Parent() {
this.constructor = Parent;
// ...
}
}
class Child extends Parent {
function Child() {
super();
this.constructor = Child;
// ...
}
}
C'est quand même incroyable que Flash qui se veut maintenant un outil de développement complètement orienté objet ait encore ce genre de disfonctionnements! :? Il ne reste plus qu'à espérer qu'un patch pour MX 2004 tant attendu par tous les développeurs Flash réctifie ce problème ... :roll:
Commentaires
euh attention sur quelques points:
- ce n'est pas parce qu'on peut ecrire du code avec des class que ce code est plus orienté objet.
- pour le constructor c'est plus délicat, car en fait avec un langage a base de ECMA262 ont est dans un heritage a base de prototype, cad de la delegation.
Au moment ou on instancie un objet par rapport a une fonction constructor on execute la chaine des constructors, oui je dit bien des constructors au pluriel, et apparemment la difference qui se passe entre le AS1 de flashMX et le AS2 de MX 2004 c'est l'ordre d'appel des constructors.
Ce n'est pas forcément un mal, tant que
avec le 1er exemple AS2:
var titi:Child = new Child();
trace( titi instanceof Child ); // true
(note j'ai pas MX 2004 pour tester)
il ne faut jamais oublier que l'on est dans un langage a base de prototype, et donc de par ce fait dynamique, et pleins de choses sont dynamiques en AS:
__constructor__, __proto__, __resolve, etc...
amha il faut etre peut-etre tester un peu plus avec instanceof que juste des ==, juste pour etre bien sur si c'est vraiment un bug ou juste un ordre différent d'appel des constructors.
Le problème, c'est que __constructor__ est calculé correctement ... (voir le post sur le typage fort dans lequel il y a une méthode clone à la fin des commentaires ...)
Et puis quelle que soit la raison, quand dans une instance j'appelle this.constructor, je m'attends logiquement à obtenir le constructeur de cette instance non ? :roll:
Ouaip, je ne vois pas bien ou tu veux en venir Zwetan...
Non, pas d'accord. Il ne peut y avoir qu'un seul ordre d'appel des constructeur: chaque constructeur doit appeler le constructeur parent en premiere instruction. Ca se fait avec super et ca marche pareil en AS1 et en AS2.
Le probleme n'est pas la de tout facon, en mettant AS2 de cote, il y a une difference entre AS1 sous Flash MX et AS1 sous Flash MX2004. Ca veut dire que du code AS1 ecrit ecrit pour MX n'est pas portable pour MX2004 et c'est ca le probleme.
EN fait, la difference vient du fait que Flash MX assigne une reference constructor sur le prototype de la classe et sur l'instance elle-meme lorsqu'elle est cree. Flash MX2004, comme Flash 5, n'assignait constructor que sur le prototype de la classe. Petit exemple:
myClass = function() {};J'ai jete un coup d'oeil au specs ECMA262 et l'assignement sur le prototype correspond aux specification. En fait, les specs ne precisent rien du tout sur le fait d'assigner une propriete constructor sur l'instance a sa creation. Ca ne veut pas dire que la specification est "intelligente" sur ce point. En effet, la specification preconise l'heritage par new, qui detruit le prototype de la classe fille, et donc l'unique reference constructor. Apres ca, constructor sera donc retrouve pus haut dans la chaine des prototypes et pointera donc sur parent, ce qui semble illogiqe. C'est exactement ce que critiquait Dave Yang dans son article. Il ne critiquait pas seulement Actionscript mais carrement la specification ECMAScript sur ce point. Ce que je pense c'est qu'en Flash 5, MM avait scrupuleusement implemente la specification. Au vu de l'article de Dave Yang, ils ont devie de la specification ECMA262 sous Flash MX et ajouter constructor sur l'instance pour "corriger" le probleme de l'heritage par new. Sous 2004, ils se sont rapprocher a nouveau de la specification ECMAScript. A noter donc que si on utilise l'heritage par __proto__, alors dans notre code d'origine, il n'y a plus de probleme:i = new myClass();
trace(i.hasOwnProperty("constructor"));
// MX: true
// MX2004: false
_global.Parent = function() {};Avec ca on obtient bien: pour Flash MX [et] Flash MX2004 (Flash 5 aussi je pense mais j'ai pas teste). Pour revenir a AS2, le fait que constructor pointe sur la classe parent voudrait dire que de facon interne, le compilateur utilise l'heritage par new. La, franchement, je ne trouve pas ca normal. Je me disais que MM s'etait rapproche a nouveau de la specification pour implementer internellement __proto__ en AS2 et laisser AS1 comme ECMAScript 3 (utiliser new). Si MM utilise new de facon interne pour AS2, ca n'a pas de sens! Pour detailler juste un peu plus, wn AS1, l'heritage par __proto__ n'est pas considere une bonne pratique parce que __proto__ ne fait pas partie de la specification ECMAScript, mais en AS2, comme ces details sont senses etre caches, ca n'a plus d'importance et Flash ne devrait pas utiliser l'heritage par new qui detruit le prototype de la classe fille. J'ai jete un coup d'oeil au swf avec flasm pour voir s'il y avait des informations supplementaires mais en il y a une nouvelle instruction "extends" dans le bytecode alors je ne peux pas etre sur a 100% que AS2 utilise l'heritage par new. Au vu des tests qu'on a fait jusqu'ici, ca en a quand meme carrement l'air. Ce qui se voit dans le swf c'est que l'heritage par extends est effectue avant la creation des methodes de la classe fille, comme c'etait le cas par new (remarque ca a l'air logique). Je fais la remarque parce que l'heritage par new, detruisant le prototype de la classe fille, DOIT etre fait avant la declaration des methodes. Avec l'heritage par __proto__, on peut declarer les methodes d'abord puis lies les prototypes ensuite. Bref, au final, comme l'a dit Lalex et comme le disait Dave Yang, this.constructor devrait logiquement pointer sur la bonne classe et pas sur la classe parent. Sinon, un autre truc, instanceOf n'est pas vraiment lie a constructor parce que c'est implemente comme ca:_global.Child = function() {};
Child.prototype.__proto__ = Parent.prototype;
Child.prototype.doIt = function() {
trace("isParent ? " + (this.constructor == Parent));
trace("isChild ? " + (this.constructor == Child));
}
var obj = new Child();
obj.doIt();
myInstanceOf = function(obj, theClass) {if (!obj || !theClass.prototype) return false;
var t = obj;
do {
if ((t = t.__proto__) == theClass.prototype) return true;
} while (t != null);
return false;
}
oui je suis d'accord que logiquement this.constructor devrait pointer sur la bonne class (fonction constructor),
mais il y a bien une difference d'ordre.
super n'appel que le constructor,
toto.prototype = new titi()
appelle le constructor ET copie le prototype de titi dans celui de toto
et ca c'est une grosse difference.
bon j'arrete d'ecrire ici (la fenettre pour ecrire est vraiment pas pratique)
et je vais un petit post sur FCNG pour poruver que on peut avoir un __proto__ et un super meme la ou on est pas censé en avoir c-a-d avec JScript v5.6
cf news://flashcodeurs.dyndns.org/flashcodeurs
Salut, petit mot en plus sur le sujet apres une semaine
Zwetan, je ne suis pas encore aller jeter un coup d'oeil sur FCNG mais je fais ca des que je peux. Je ne connaissais pas cette ressource en fait, mais ca a l'air tres sympathique.
Sinon, j'ai achete une version pas chere de ADSG2 (en Inde) et je place un petit extrait ci-dessous. En gros, concernant le AS1, ce que je pensais etre un fixe sous Flash MX etait un bug aux yeux de Macromedia et le fait que ca ne marche plus de nouveau sous MX2004 est "normal" (ou du moins suit les specifications ECMA-262).
Citation de Colin Moock:
Donc, oui, Macromedia a bien "corriger" ce bug en MX2004. Cela dit, Colin lui meme qualifie le procede de non-intuitif concernant l'heritage par new en AS1:
Apres ca, Colin decrit la plupart des procedes d'heritage non-standard, pour eviter le comportement normal (qui est pourri).
Enfin bref, la question maintenant c'est est-ce que ce comportement est aussi "normal" en AS2? J'ai pas lu les specs de ECMA-262 v4 mais j'essairai de jeter un coup d'oeil quand j'ai le temps. Je suis presque pret a parier qu'il n'y a meme pas de propriete constructor en ECMAScript v4 en fait. Est-ce que quelqu'un qui travaille avec JScript.NET peut nous donner des details?
Timoth'
Et aller, un dernier pour la route
Dans les specs swf v7, voila ce qu'n lit sur l'instruction extends:
1 Pops the ScriptObject superclass constructor off the stack.
2 Pops the ScriptObject subclass constructor off the stack.
3 Creates a new ScriptObject.
4 Sets the new ScriptObject’s __proto__ property to the superclass’ prototype property.
5 Sets the new ScriptObject’s __constructor__ property to the superclass.
6 Sets the subclass’ prototype property to the new ScriptObject.
These steps are the equivalent to the following ActionScript:
Subclass.prototype = new Object();Alors, c'est pas ce que je disais: il n'y a pas d'heritage par new avec appel du constructeur de la superclasse mais cette ligne est debile:Subclass.prototype.__proto__ = Superclass.prototype;
Subclass.prototype.__constructor__ = Superclass;
Subclass.prototype = new Object();C'est elle qui detruit le prototype existant et donc la propriete constructor qui y residait. Je ne vois vraiment pas pourquoi ils ont fait ca, a moins de vouloir garder la compatibilite avec AS1 mais dans ce cas c'est quand meme rate parce que dans AS1, on est sense appele le constructeur de la superclasse...
Pas cool
Timoth'
C'est clair que c'est complètement stupide leur truc ! :? Etant donné que les deux dernières lignes suffisent amplement pour l'héritage ... 8|
J' essaie de porter en AS2 la classe Vector écrite par Robert Penner, qui utilise régulièrement l'instruction:
return new constructor() pour retourner un nouveau vecteur.
Flash me donne le message suivant:
A function call on a non-function was attempted. return new constructor();
Ce que je ne comprends pas c'est que si je trace typeof(constructor), il me dit que c'est une fonction.
Auriez-vous une idée quand à la cause de ce comportement, voire une autre manière de procéder?
Merci d'avance
Ph.
désolé si je vous ai dérangés, mais cette conversation était tellement passionnante
Pour mon petit problème posté ce matin, j'ai trouvé une solution comme ceci:
*ds la classe Vector
var v:Vector
v = new Vector(this.x,this.y)
return v;
**au lieu de
return new constructor(this.x,this.y);
je ne sais pas si c'est la meilleure des manières mais en tous les cas ça ne me dit plus d'insanités :))
ph.
Bonjour,
en fait la réelle question est : à quoi sert constructor lorsque l'on programme en AS2?
AS2 a le meme probleme que c++, il a gardé les "merdes" de C, ici AS1.
Votre blog est en fait un faux probème
Alain
ps :j'ai également usé et abusé de constructor mais c'était dans une autre vie, en AS1
(la preuve :http://www.eyrolles.com/Informatique/Livre/9782212110685/livre-flash-mx---jeux-en-reseau-avec-actionscript-et-xml.php?xd=90607f16ccc7446b55deb25da41b5fdd)
A mon avis, le constructor devrait être une manière de palier le manque d'un instruction de clonage ...
Il a été établi que cela ne constitue pas un bug, dans le sens ou la norme ECMA décrit la propriété constructor de la même manière ...
Il s'agit ici de logique toute bête directement liée à la norme ECMA justement : quand j'écris this.constructor, n'est-il pas normal de d'attendre à récupérer le constructeur de l'objet ? :o
++ ^^
normalement il est fait pour cela... donc lui virer ses attributes c un peu dommage.
Reste qu'on peut notre propre constructor... moi j'ai fait le mien ,il marche bien et m'évite d'utiliser la version non docu.
eka > Il y a peu de chances que __constructor__ soit un jour deprecated, étant donné qu'il est utilisé par le bytecode de l'instruction extends.
Sinon, comment utilises-tu le constructor ? Moi je mettrais dans le constructeur
class MAClasse {function MaClasse () {
this.constructor = arguments.callee;
}
}
++ ^^
en fait en général si je l'utilise directement j'utilise ce que tu fais au dessus.

Pour le reste j'utilise une classe qui me permet de l'obtenir via le nom de la classe (un String)
Voilà ma classe qui me permet de gérer au mieux mes Classes
/* ----------------
Name : ClassFactory
Package : com.eka.core
Version : 1.0.0
Date : 2003-03-10
Author : ekameleon
URL : <a href="http://www.ekameleon.net" rel="nofollow">http://www.ekameleon.net</a>
Mail : <a href="mailto:contact@ekameleon.net" rel="nofollow">contact@ekameleon.net</a>
DESCRIPTION :
Classe statique qui permet de gérer les Classes et leurs constructeurs.
PUBLIC METHOD
- getConstructorOf ( class:String )
Description :
Permet de récupérer le constructeur (constructor) d'une classe passée en paramètre sous la forme d'une chaine.
Usage
import com.eka.core.ClassFactory ;
ClassFactory.getConstructorOf (classPath) ;
Paramètres
- classPath : une chaine de caractère représentant le chemin de la classe.
Renvoi : le constructeur (type Function) de la Classe
Exemple :
import com.eka.core.ClassFactory ;
var constructor:Function = ClassFactory.getConstructorOf ("package.MaClass") ;
- getInstanceOf ( class:String , params:Array)
Description :
Permet de récupérer le constructeur (constructor) d'une classe passée en paramètre sous la forme d'une chaine.
Usage
import com.eka.core.ClassFactory ;
ClassFactory.getConstructorOf (classPath) ;
Paramètres
- classPath : une chaine de caractère représentant le chemin de la classe.
- params : un tableau contenant les valeurs définissant les paramètres du constructeur de la classe.
Renvoi : une instance d'une classe.
Exemple :
class : /package/MyClass.as
class package.MyClass {
// constructor
function MyClass (msg:String) {
trace (msg) ;
}
}
flash :
import com.eka.core.ClassFactory ;
var instance:package.MyClass = ClassFactory.getConstructorOf ("package.MyClass", ["so good"]) ;
Remarque importante !!
- Dans l'exemple précédent pour que la variable instance puisse récupérer en valeur l'instance nouvellement créer,
il est important de typer la variable correctement, sans cela instance renverra undefined.
----------------*/
class com.eka.core.ClassFactory {
// ----o Author Properties
public static var className:String= "ClassFactory" ;
public static var classPackage:String= "com.eka.core";
public static var version:String= "1.0.0";
public static var author:String= "ekameleon";
public static var link:String= "<a href="http://www.ekameleon.net" rel="nofollow">http://www.ekameleon.net</a>" ;
// ----o Static Public Methods
static public function getConstructorOf ( _pName : String ) : Function {
var classPath:Array = _pName.split(".");
var package:Object = _global ;
var n:Number = classPath.length - 1 ;
for (var i:Number=0 ; i < n ; i++) package = package[classPath[i]] ;
return package[ classPath [classPath.length-1] ] ;
}
static function getInstanceOf ( _pName : String , params:Array ) : Object {
var _constructor:Function = getConstructorOf (_pName) ;
var instance = new _constructor () ;
if (params != undefined) _constructor.apply (instance, params) ;
return instance ;
}
}
bye
Euh il y a une erreur de ma part dans les commentaires de la méthodes getInstanceOf au dessus... je pense que vous l'aurez compris
Fil des commentaires de ce billet