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 ! >_> |
| 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"); |
| 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() {} |
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 |
| 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"); |
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 |
- 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 lu gratuitement 7 177 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
