Contents
Vraiment, j’adore Javascript. Mais néanmoins, il faut reconnaître que ce langage offre un tel nombre de syntaxes différentes pour réaliser une même opération que ça en devient parfois un peu bordélique.
Petit exemple avec l’écriture d’un objet. J’ai compté six façons différentes d’obtenir un objet (et j’en ai certainement oubliées).
Simple objet littéral
La syntaxe
Le truc cool avec javascript, c’est qu’on peut déclarer un simple objet à la volée (sans déclarer de classe) et de manière littérale (sans syntaxe d’instanciation). Il s’agit souvent soit d’une structure de donnée (par exemple pour passer des options à une classe), soit d’une instance unique d’objet.
1 2 3 4 5 | var foo = { bar: 'bzzz' }; console.debug(foo.bar); //'bzzz' |
Le problème
Cette syntaxe convient parfaitement si la structure de l’objet ne doit pas assurer une intégrité absolue (on peut se passer de définir certaines propriétés) ou être reproductible avec fidélité (on peut se passer de la déclaration d’une signature par l’intermédiaire d’une classe). Cela remplit bien le rôle d’un array associatif en PHP, par exemple.
Par contre, cela ne donne pas d’assurance quant à la complétude de l’objet, et cela devient plus embêtant si on doit créer plusieurs objets du même type avec fidélité.
Object.create()
La syntaxe
Avec ES5, on sait instancier un objet depuis une définition, grâce à Object.create(). Basiquement, cela ressemble un peu à une instanciation de classe dynamique. Cela permet beaucoup de choses en pratique car plutôt que de travailler avec une définition, on peut utiliser directement le prototype d’une classe. On sait réaliser alors une sorte de clone du prototype.
1 2 | var foo = Object.create({}, { bar: { value: 'bzzz' } }) console.debug(foo.bar); //'bzzz' |
Le problème
Ok, si on rend le code réutilisable (par exemple, en stockant la définition dans une fonction faisant office de constructeur), on est sûr d’avoir un objet formé correctement lors de chaque instanciation. Par contre, il n’y a pas de réelle plus-value par rapport à la déclaration d’une classe si on ne travaille pas au niveau du prototype.
Objet créé par une fonction
La syntaxe
Pour remédier autrement au problème de l’intégrité, on peut imaginer utiliser une fonction qui retourne systématiquement la même structure de donnée. Une factory en somme.
1 2 3 4 5 6 7 8 | function createFoo(bar){ return { bar: bar }; } var foo = createFoo('bzzz'); console.debug(foo.bar); //'bzzz' |
Le problème
En appelant une fonction, on perd un peu la syntaxe objet, et donc la notion de ce qui est retourné par la fonction. On n’a plus « l’impression » de travailler avec un objet…
Objet littéral instancié
La syntaxe
On peut agrémenter la syntaxe précédente d’un new. Le résultat sera exactement identique, si ce n’est qu’on devient plus explicite quant au comportement de la fonction.
1 2 3 4 5 6 7 8 | function Foo(bar){ return { bar: bar }; } var foo = new Foo('bzzz'); console.debug(foo.bar); //'bzzz' |
Le problème
Le problème c’est qu’on peut être dupé par le faux typage. En effet, le résultat cette opération est bien l’instanciation d’un nouvel objet de type Object depuis l’objet littéral retourné par la fonction. Il ne s’agit pas de l’instanciation de la fonction en tant que telle, comme dans le processus d’instanciation depuis une classe.
1 | console.debug(foo instanceof Foo); //false |
D’autre part, c’est assez vicieux comme syntaxe. On ne sait pas vraiment ce qui est instancié: est-ce l’objet ou la fonction. Surtout que, normalement, il n’est pas possible d’instancier un autre objet.
1 2 3 4 5 | var foo = { bar: 'bzzz' }; var foo2 = new foo; //Uncaught TypeError: foo is not a function(…) |
Objet instancié depuis une classe
La syntaxe
Comme on le sait, on peut déclarer une classe depuis une fonction. Du coup, l’objet sera effectivement une instance de la fonction. Le problème précédent est résolu.
1 2 3 4 5 6 7 | function Foo(bar){ this.bar = bar; } var foo = new Foo('bzzz'); console.debug(foo.bar); //'bzzz' console.debug(foo instanceof Foo); //true |
Le problème
Toutefois, on se retrouve avec une erreur un peu bizarre si on oublie le new (faut le vouloir aussi, mais bon…).
1 2 | var foo = Foo('bzzz'); foo.bar;//Uncaught TypeError: Cannot read property 'bar' of undefined |
Et si jamais la fonction retournait le « this », on se retrouvait avec l’objet global… argh!
Les classes ES2015
La syntaxe
ES2015, aka ES6, propose une nouvelle syntaxe de déclaration de classe plus explicite (ou en tout cas plus traditionnelle). On est obligé de passer par le new et l’objet est bien une instance de la classe.
1 2 3 4 5 6 7 8 9 | class Foo{ constructor(bar){ this.bar = bar; } } var foo = new Foo('bzzz'); console.debug(foo.bar); //'bzzz' console.debug(foo instanceof Foo); //true |
Le problème
On s’est vraiment éloigné de l’esprit de liberté offert par les objets littéraux du premier exemple. On se retrouve avec un code plus lourd (mais plus réutilisable) qui se rapproche de l’écriture traditionnelle de la programmation orientée objet.
Conclusions
Et donc, je me pose la question: Comment bien écrire un objet en javascript?… tout dépend du contexte!
Tu peux aussi utiliser les closures pour limiter ce qui est publique et privé ^^
Avec cette syntaxe, tu masques la méthode « accelerate »
La façon dont on accède aux propriétés pourrait faire l’objet d’un article à part entière! Tins, c’est une idée…
D’ailleurs, je n’en suis pas fan de cette notation.. J’aime bien comme toi mixer les deux selon mes besoins, mais ca peut être très sympa et ca me donne une idée
https://jsfiddle.net/4m3x3L8r/
Tu présentes des approches différentes qui ne font pas du tout la même chose, avec des concepts différents.
C’est un peu un gros bordel.
« Et donc, je me pose la question: Comment bien écrire un objet en javascript?… tout dépend du contexte! »
Exactement, mais bon avec un peu plus de texte ce serait mieux:
Structurer de la data simple: Objet littéral
Système de collection: Classe/Prototype (ce que tu as sous la ref Objet instancié depuis une classe).
Ensuite c’est le gout les couleurs, si tu veux manipuler des trucs en private sans te faire chier à créer une classe ES2015 ou son équivalent un poil plus verbeux ES5 une fonction instanciée (Prototype), tu utilises une factory:
Comme dans tout langage ça dépend du besoin, faut pas se prendre la tête et rester dans le simple. On ne fait pas du Java mais du JavaScript, donc si je veux un objet qui _ressemble_ à une map, je fais un {}.
Object.create osef, on s’en sert si tu veux faire des trucs plus avancés (genre dans 90% des cas osef).
KISS bordel.
En espérant que ça va répondre à ton questionnement. Le JS c’est pas si complexe, suffit de faire comme partout, avoir une approche KISS.
D’ailleurs une super technique est celle du canard (Google inside ^^)
Yup ça marche plutôt bien
Petite erreur dans ta remarque : var foo = Foo(‘bzzz’); // Uncaught TypeError: foo is not a function(…)
Autre petite remarque tout dépends de ton approche, si on parle d’instancier des objets de même type ou fonctions il y a class/prototype sinon ce sont des hash/collection que tu fais et dans ce dernier cas je ne vois pas l’intérêt d’avoir une classe ou un prototype.
Pour moi il n’y a que ses méthodes là pour créer les object, après si tu encapsule la création dans une function c’est la même chose.
corrigé! merci bcp! (pour info, c’est foo qui est undefined, car la fonction ne retourne rien.)
En fait, si tu pousses ton raisonnement encore plus loin, il n’y a qu’une façon de faire un objet, car même ce que tu appelles hash/collection, n’est en fait que l’instanciation littérale de la classe Object…
Moi je parle de différences syntaxiques, pas conceptuelles. Par exemple, la déclaration une classe en ES2015 peut s’écrire différemment, bien que conceptuellement il s’agisse exactement de la même chose qu’avec les prototypes.
PS: Est-ce que ton code ne tombe pas dans une récursion infinie?: « if(this instanceof Point) return new Point(x,y); »
effectivement c’est une boucle infini il manque le !(négation).
Merci pour la remarque
Le code ne boucle pas ça test que le this c’est bien un object de Point. car quand tu fais `new MyClass` il y a instanciation de myClass puis assignation de l’instance au `this` mais quand tu fais `MyCass()`, le `this` c’est l’object global(window) donc pour tester la presence de `new` on test si c’est une instance de myClass. Si c’est pas le cas on envoie une instance de myClass.
Bon après maintenant (ES2015) tu peux tester `new.target` pour vérifier si c’est une instanciation ou un appel de fonction.
ah ok, alors le test devrait être inversé:
pas con, j’y avais jamais pensé!
donc, encore une autre syntaxe..
Mdrrr… et oui encore une syntaxe…