-- new
Si on lit la spécification ES3, on s'aperçoit :
- que toutes les fonctions peuvent se comporter comme des constructeurs. Elles ont, associé, un prototype qui permet par exemple l'appel new ({}.toString)() que cela ait un sens ou non.
- qu'il y a une distinction sémantique entre appel comme constructeur et appel comme fonction, justement avec l'opérateur 'new'.
- qu'enfin, la spécification distingue explicitement ces différentes formes d'appel dans le cas des objets 'builtin' :
Constructeurs appelés comme fonction :
- Conversion de type : { Object, String, Boolean, Number }
- Appel comme constructeur : { Function, Array, Error }
- Autres :
-- Date([...]) <=> (new Date()).toString()
-- RegExp(<RegExp>) <=> <RegExp>, et RegExp(...) <=> new Regexp(...)
Je n'arrive (vraiment) pas à comprendre, le problème avec 'new'. Je comprends bien que, si la fonction est écrite comme un constructeur, c'est à dire utilisant 'this' pour modifier l'objet, cela pose un problème si on oublie le 'new'. Mais :
1- c'est une erreur de programmation assez grossière, de débutant, un peu comme d'oublier un '=' dans un test d'égalité (peu de chance qu'un dev Java oublie 'new' !)
2- pourquoi avoir lié 'this' au contexte global hors de l'utilisation de 'new' ou d'un appel 'object.method()' où 'this' se comporte comme on peut l'attendre ?
Si vraiment c'est un gros problème, pourquoi diminuer la sémantique du langage en évitant 'new' ? Pourquoi au contraire ne pas en profiter pour proposer des constructeurs multi-fonctionnels ? ou à minima, gérer le problème
1 2 3 4 5
| function Foo ( bar ) {
if ( !(this instanceof Foo) )
return new Foo(bar);
this.bar = bar;
} |
Comme ça, on vérouille le cas : var foo = Foo(...);, et on se retrouve dans le même cas que 'Function', 'Array', etc. Voire pour les plus angoissés une fonction encapsulant le constructeur :
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Constructor ( Construct ) {
return (function Ctor (/*arguments*/){
if ( !(this instanceof Ctor) )
return Ctor.apply(
Object.create(Ctor.prototype),
arguments );
return Construct.apply(this, arguments);
});
}
var Foo = Constructor(function( bar ) {
this.bar = bar;
)};
// Foo(..) <=> new Foo(...) |
On peut remarquer au passage qu'on traduit l'appel de fonction vers l'appel comme constructeur et que donc il vaut mieux utiliser 'new' : on évite ainsi un appel de fonction supplémentaire.
-- Object.create
C'est subjectif, mais je trouve le modèle d'héritage mis en place en Javascript assez élégant. La récursivité entre 'Object', 'Function' et leurs protoypes me rappelle (un peu) ObjVLisp.
En tout cas, on avait (pré ES5) un graphe d'héritage cohérent avec 'Object.prototype' comme racine (hors types primaires et objets externes).
ES5: introduction de 'Object.create' ... que permet cette fonction qu'on ne pouvait pas faire avant ? presque rien (je ne parle pas de 'Object.defineProperty') sauf ...
casser le graphe d'héritage. On peut créer des objets 'Object.create(null)', qui ne sont pas des types primaires, pas des objets externes mais qui se retrouvent hors du graphe d'héritage.
Sur le fond, pourquoi pas. Mais c'est quand même une rupture de taille. Avant d'appeler n'importe quelle methode de 'Object.prototype', il va falloir trouver un moyen de savoir si l'objet hérite ou pas de 'Object.prototype'. Quand on voit les acrobaties de code, ne serait-ce que pour savoir si un objet est un tableau ...
Si au moins on avait eu typeof object == "what do you mean exactly by 'typeof' ?".
Le coté positif, c'est que cela étend les capacités du langage.
-- D. Crockford
Sa fonction si je la comprends est une tentative à destination de (qui?) pour fournir un modèle de "classe" simplifié :
- possibilité d'hériter d'un constructeur (extend.prototype)
- séparation des attributs (initializer) et des méthodes (méthods)
- graphe d'héritage rendu statique au niveau des constructeurs (cloture du prototype)
C'est une approche. Je ne vois pas ce qu'elle apporte à part augmenter la confusion en créant des représentations qui ne sont pas celle du langage.
S'il avait conservé le 'new' cela aurait donné de la cohérence puisque qu'en général dans les langages de classe, 'new' est utilisé. Là au contraire, si on utilise 'new' on va forcer le langage a créer un objet, établir le lien d'héritage et tout mettre à la poubelle, puisque qu'on le snobe en refaisant le processus de l'autre coté ... Mais impossible de conserver 'new' puisque que justement, pour avoir un graphe d'héritage statique, le prototype est dans la cloture et que l'utilisation de 'new' risquerait de casser ce graphe d'héritage. Autrement dit, pour gérer une vision plus commune des langages de classes, il introduit lui même une rupture par rapport à ces langages.
La seule chose que je ne comprends pas c'est le test systèmatique de 'initializer' comme fonction ? comme il est dans la cloture il aurait pu optimiser ça.
Sur le fond, j'ai un peu de mal avec ses prises de position : il ne faut pas utiliser 'eval', 'switch', 'with', 'for(var', et je ne sais plus quoi encore. D'état d'esprit, je lui préfère Bjarne Stroustrup qui dit, en parlant du C++, quelque chose comme : utilisez n'importe quelle possibilité du langage tant que vous le faites à bon escient.
Quoi qu'il en soit, bon article qui a le mérite d'exposer un point de vue argumenté.
Partager