JavaScript : L'opérateur new, bonne ou mauvaise pratique ?
Sylvain Pollet-Villard donne son opinion sur la question

Le , par SylvainPV, Rédacteur/Modérateur
L'opérateur new est-il une mauvaise pratique ?

On sait que l'opérateur new a été introduit en JavaScript pour rendre la programmation objet plus intuitive pour les développeurs accoutumés à la programmation objet orientée classes. La syntaxe est effectivement plus facile à prendre en main, mais présente aussi plusieurs inconvénients.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
function Car(model){ 
    this.model = model; 
   this.speed = 0; 
} 
 
Car.prototype.wheels = 4; 
Car.prototype.drive = function(){ 
    this.speed = 120; 
} 
 
var car = new Car("Ferrari"); 
car.drive(); 
console.log(car.speed); // 120

L'obligation d'avoir une fonction constructeur

En POO par classes, un constructeur est nécessaire pour passer du modèle à l'objet réel, de la classe à l'instance. En POO par prototypes, le modèle est un objet réel. Une fonction constructeur n'est donc pas obligatoire. C'est un petit plus qui nous sert à passer les propriétés initiales de l'objet en inline, et appeler éventuellement d'autres fonctions ou déclencher des évènements à la création de l'objet. Avec new, nous sommes forcés d'avoir un constructeur. Et ce que le développeur assimile à la classe est en fait la fonction constructeur, ce qui est très perturbant.

Code : Sélectionner tout
1
2
3
car.constructor === Car; // true 
 
car instanceof Car; //true : si car est une instance de Car, alors Car est ma classe ? Non ! C'est le constructeur ! >_>
Et si je souhaite ajouter le constructeur de la voiture dans mon modèle Car ?

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
function Car(constructor, model){ 
   this.model = model; 
   this.constructor = constructor; 
   this.speed = 0; 
} 
 
var golf = new Car("Volkswagen","Golf"); 
 
golf.constructor === Car; // false !

L'héritage devient sans raison bien plus compliqué

C'est là que la plupart des développeurs Java sont complètement largués. En suivant l'approche new, un héritage se ferait de la sorte :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
 
function Berline(constructor, model){ 
    Car.apply(this, arguments); //équivalent de super(); 
} 
 
Berline.prototype = new Car(); // le modèle de la classe fils est une instance de la classe parente ??? 
 
var volvo = new Berline("Volvo","S60");
Pour expliquer cela, il faut se rappeler que les prototypes sont des objets "réels", instanciés. Seulement, Car n'est pas le prototype, mais le constructeur du prototype ! Il ne faut pas confondre non plus l'instance et son constructeur.

Code : Sélectionner tout
1
2
3
4
 
Object.getPrototypeOf(volvo); // Car {model: undefined, constructor: undefined, speed: 0} 
Object.getPrototypeOf(volvo) === Car //false 
Object.getPrototypeOf(Berline); // function Empty() {}
Tout cela est vraiment perturbant, et source de fréquentes erreurs. Nous sommes contraints de garder une référence aux constructeurs, mais ceux-ci n'ont aucune utilité à part leur usage avec new.

Le contexte this fonctionne différemment

Avec new, this ne fonctionne plus de la même façon dans la fonction constructeur. Il ne fait pas référence au scope parent comme d'habitude, mais à l'instance nouvellement créée. C'est un comportement tout à fait spécial et là encore très perturbant. Cela a aussi comme grave conséquence de modifier le contexte parent par erreur si on oublie l'opérateur new à l'instanciation :

Code : Sélectionner tout
1
2
3
4
 
var toyota = Car("Toyota","Yaris"); //oups 
console.log(window.model); // Yaris ! GLOBAL LEAK 
console.log(window.constructor); // Toyota ! Aïe, j'ai même écrasé Window ! Bonjour les dégâts !

L'alternative : Object.create (support IE9+)

Voici un code similaire sans utiliser l'opérateur new :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
/* le prototype de base de tout objet est Object.prototype 
   le prototype Car est un objet, on le crée via Object.create et on lui donne les propriétés qu'auront toutes les voitures */ 
var Car = Object.create(Object.prototype); 
Car.wheels = 4; 
Car.drive = function(){ 
   this.speed = 120; 
}; 
 
// on crée une voiture à partir du prototype Car 
var golf = Object.create(Car); 
golf.constructor = "Volkswagen"; 
golf.model = "Golf"; 
 
Object.getPrototypeOf(golf) === Car; // true ! ENFIN 
 
golf instanceof Car // TypeError: Expecting a function in instanceof check, but got #<Object> ; l'opérateur instanceof est à jeter avec new
Si on veut une fonction constructeur, on la déclare :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
 
Car.create = function(constructor, model ){ 
     return Object.create(Car, { 
         constructor: { writable:true, configurable:true, value: constructor }, 
         model: { writable:true, configurable:true, value: model}, 
      }); 
}; 
 
var volvo = Car.create("Volvo","S60");
Notez les attributs writable, configurable... Il y en a d'autres, répertoriés ici : https://developer.mozilla.org/fr/doc...defineProperty
On peut paramétrer très précisément le comportement de la variable, et se rapprocher de la définition de variables privées ou semi-privées en surchargeant les getters et setters.

Le concept de l'héritage s'assimile très bien à celui de prototype :

Code : Sélectionner tout
1
2
3
4
 
var Berline = Object.create(Car); 
var peugeot = Object.create(Berline); 
console.log(peugeot.wheels); //4
Résumé des avantages :
  • libre d'utiliser une fonction constructeur ou d'attribuer les propriétés manuellement ;
  • plus d'utilisation hasardeuse du mot-clé this ;
  • on garde une référence au prototype et non plus au constructeur ;
  • l'héritage est aussi simple qu'il devrait l'être.


Pour ceux qui ne peuvent pas se passer de constructeurs et qui souhaitent éviter d'en définir un pour chaque objet, Douglas Crockford a travaillé sur une fonction qui remplace efficacement l'opérateur new sans perdre les avantages précités. La voici :


Douglas CrockFord définit son propre "new", septembre 2011



Et vous ?

Qu'en pensez-vous ? Doit-on bannir new et instanceof ?


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de Watilin Watilin - Membre expert https://www.developpez.com
le 21/05/2014 à 3:28
Si on essaye de penser avec sagesse et ne pas foncer tête baissée dans la nouveauté (comme j'ai l'habitude de faire ), on peut constater qu'il y a une équivalence assez forte entre le modèle actuel à base de new et de constructeurs, et le modèle « prototype seulement » qui est assez bien implanté aujourd'hui, et se dire que les deux sont bien, que ce n'est qu'une affaire de goût.

Code : Sélectionner tout
1
2
3
constructeur    <->  prototype 
new             <->  Object.create 
a instanceof b  <->  a.prototype === b
Les habitués de Java qui auront fait l'effort de s'adapter à l'étrange hybridation que fait JavaScript avec l'opérateur new n'ont peut-être pas envie d'avoir à apprendre un nouveau paradigme. Personnellement, je considère que cette possibilité de choisir son paradigme est une faiblesse du langage (c'est un peu du racolage et ça rend les interpréteurs compliqués), mais d'autres peuvent voir ça comme une force, une souplesse. Pour en rajouter une couche, il y a ES6 qui arrive et qui propose d'introduire les class. Quelqu'un sur DVP (c'était peut-être bien toi d'ailleurs) a dit il n'y a pas longtemps « il faut choisir un paradigme au début de son projet et s'y tenir, sinon ça peut vite devenir un joyeux bordel » ou quelque chose comme ça. Je prédis que pour les 10 prochaines années, les pires exemples de code spaghetti seront écrits en JavaScript

Même si tous les développeurs du Web se mettaient d'accord pour se débarasser de tel ou tel paradigme, dans le but de simplifier les interpréteurs et les rendre plus performants, ça ne serait pas possible car ça casserait la rétro-compatibilité. Cela dit, je dois avouer – attention opinion personnelle – que le paradigme « prototype seulement » me paraît merveilleusement simple et clair.

Peut-être qu'à l'avenir, les développeurs web pourront indiquer au début de leur script quel paradigme ils ont choisi, et l'interpréteur pourra basculer en « mode constructeur », en « mode prototype » ou en « mode classe » pour tourner plus efficacement. En fait, ça me fait mal à la tête d'essayer de comprendre comment un interpréteur actuel est capable de gérer 3 paradigmes différents
Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 21/05/2014 à 10:29
Oui, tu dois parler de ce post ci

Je suis d'accord avec toi, ce choix de paradigme apporte plus de problèmes qu'il n'en résout. Dans la vidéo, Douglas Crockford dit à juste titre que historiquement, JavaScript n'a jamais été fier de sa nature prototypale. Ça fait des décennies qu'on a le cul entre deux chaises, si vous me permettez l'expression. Je me demande bien pourquoi Object.create() est apparu après new, alors que ECMAScript est orienté prototypes depuis ses origines.
Avatar de skyzoboy skyzoboy - Nouveau Candidat au Club https://www.developpez.com
le 21/05/2014 à 14:25
Je me demande bien pourquoi Object.create() est apparu après new, alors que ECMAScript est orienté prototypes depuis ses origines.

ActionScript a pris la même direction. Il n'est plus maintenant basé sur les prototypes. J'espère que JavaScript prendra le même chemin ...
Avatar de Kaamo Kaamo - Membre expert https://www.developpez.com
le 21/05/2014 à 15:11
Tu sais que je suis d'accord avec toi Sylvain et que je préfère le pattern prototype Object.create au pattern constructor new Function.
Mais ... le new, et donc constructor, est trop ancré dans le paysage de JavaScript pour ne pas l'utiliser en production.

En gros, je pense que tant qu'on est dans la norme ES5, il faut utiliser new en production. A l'heure actuelle, les interpréteurs sont taillés pour gérer des instances créées à partir de constructor.

Les avantages de new pour créer des instances :
- Les moteurs JavaScript du marché accèdent à leur propriétés très rapidement grâce notamment aux hidden class
- ça entre mieux dans le paysage de JavaScript (pour créer des instances, autre que passer par la forme littérale, il faut utiliser new. Ex : new Date(), new Boolean(), etc. et non date = Object.create(Date))
- Dans le prochain ES6, il y a les class qui débarquent. Celles-ci utiliseront les constructeurs.

On a beau vouloir évangéliser les débutants, je pense que les constructeurs font partie intégrante, et inhérente, à JavaScript (du moins pour ES5). Faut donc être conscient des dangers.
Mais sur un projet, je n'ai jamais vu un lead-dev prendre la décision d'utiliser le pattern Object.create. Les débutants seraient trop perdus malheureusement.
On est donc coincé avec ce foutu new
Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 21/05/2014 à 17:43
Bien sûr, je n’espérais pas arrêter d'utiliser new du jour au lendemain. C'est comme un vieux chewing-gum collé sous une semelle, on l'a depuis des mois mais on a jamais le courage de s'arrêter pour le gratter. Et plus le temps passe, plus il est difficile à décoller... Alors on apprend à vivre avec, on commence à s'y attacher, et ça se termine par l'ajout de class en ES6 (oui je trolle )

Ça fait un petit moment que je voulais mettre par écrit quelques codes et arguments expliquant pourquoi, selon moi, Object.create est bien moins perturbant à utiliser que new. Si les débutants devraient se sentir perdus, c'est pour de mauvaises raisons ; principalement tous les tutos, codes existants et certaines API dont Date qui sont basés sur les constructeurs. Et il y a encore et toujours la lutte des paradigmes (et non pas la lutte des classes, n'est-ce pas Marx) qui ne réussit pas trop aux prototypes, la loi de la majorité l'emportant sur le reste.

En parlant de Date :
Code : Sélectionner tout
1
2
3
4
5
Date() == new Date() // true 
+(Date()) == +(new Date()) //false 
(new Date()).constructor === Date //true 
new ((new Date()).constructor)() == new Date() //false 
+(new ((new Date()).constructor)()) == +(new Date()) //true
Avatar de danielhagnoul danielhagnoul - Rédacteur https://www.developpez.com
le 21/05/2014 à 21:20
Bonsoir

Il est clair qu'il faut choisir et utiliser une seule méthode de travail.

En parlant de création d'objet, de Object.create() et de Date :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 
 * Object.create( proto [, propertiesObject ] )  
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create 
 *  
 * On peut créer un objet valide de trois manières : 
 */ 
	var Obj1 = {}, 
		Obj2 = new Object(), 
		Obj3 = Object.create( {} ); 
		 
	console.log( Obj1 instanceof Object ); // true 
	console.log( Obj1.constructor ); // function Object()  
 
	console.log( Obj2 instanceof Object ); // true 
	console.log( Obj2.constructor ); // function Object() 
 
	console.log( Obj3 instanceof Object ); // true 
	console.log( Obj3.constructor ); // function Object()  
 
/* 
 * On peut créer un nouvel objet à partir du prototype d'un autre : 
 */ 
	var MonObjDate = Object.create( Date.prototype ); 
	 
	console.log( typeof MonObjDate ); // object 
	console.log( MonObjDate === Date ); // false 
	console.log( MonObjDate instanceof Date ); // true 
	console.log( MonObjDate.constructor ); // function Date() { [native code] }
Avatar de Watilin Watilin - Membre expert https://www.developpez.com
le 21/05/2014 à 23:03
Code : Sélectionner tout
	console.log( Obj1.consructor ); // undefined
Daniel, sérieusement, tu croyais nous avoir comme ça ?

Code : Sélectionner tout
1
2
3
console.log( Obj1.constructor ); // function Object() 
console.log( Obj2.constructor ); // function Object() 
console.log( Obj3.constructor ); // function Object()
Avatar de danielhagnoul danielhagnoul - Rédacteur https://www.developpez.com
le 22/05/2014 à 0:52
Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 22/05/2014 à 0:58
Obj3 n'est pas strictement équivalent à Obj1 et Obj2, il faudrait écrire :
Code : Sélectionner tout
1
2
3
4
5
Obj3 = Object.create(Object.prototype); 
// ou  
Obj3 = Object.create(null); 
Object.getPrototypeOf({}) === Object.prototype // true 
Object.getPrototypeOf(Object.prototype) // null
pour ne faire qu'une instanciation d'objet au lieu de deux.
Avatar de danielhagnoul danielhagnoul - Rédacteur https://www.developpez.com
le 22/05/2014 à 11:11
Oui !
Offres d'emploi IT
Architecte sécurité des systèmes d'information embarqués H/F
Safran - Ile de France - 100 rue de Paris 91300 MASSY
Architecte et intégrateur scade/simulink H/F
Safran - Ile de France - Vélizy-Villacoublay (78140)
Ingénieur analyste programmeur (H/F)
Safran - Auvergne - Montluçon (03100)

Voir plus d'offres Voir la carte des offres IT
Responsable bénévole de la rubrique JavaScript : Xavier Lecomte -