Quelles sont les différences entre ces trois modèles de définitions de «classe» dans JavaScript?

Existe-t-il des différences importantes / subtiles / significatives sous le capot lors du choix d'utiliser l'un de ces quatre modèles sur les autres? Et, y a-t-il des différences entre elles lorsqu'elles sont "instanciées" via Object.create() contre le new opérateur?

1) Le modèle utilisé par CoffeeScript lors de la traduction des définitions de «classe»:

 Animal = (function() { function Animal(name) { this.name = name; } Animal.prototype.move = function(meters) { return alert(this.name + (" moved " + meters + "m.")); }; return Animal; })(); 

et

2) Le modèle que Knockout semble favoriser:

 var DifferentAnimal = function(name){ var self = this; self.name = name; self.move = function(meters){ return alert(this.name + (" moved " + meters + "m.")); }; } 

et

3) un modèle similaire, simple que j'ai souvent vu:

 var DifferentAnimalWithClosure = function(name){ var name = name; var move = function(meters){ }; return {name:name, move:move}; } 

et

4) Le modèle que Backbone favorise:

 var OneMoreAnimal= ClassThatAlreadyExists.extend({ name:'', move:function(){} }); 

Mise à jour 1: Modification du modèle # 2 et modèle # 3 en réponse à la réponse d'Elias // formatage mineur

Juste pour être clair: JS ne connaît pas les classes, juste des objets et des fonctions de constructeur personnalisées, définies par eux-mêmes, mais ce n'est pas loin.
Pour répondre à votre question en bref: oui, il existe des différences petites et même assez importantes entre les différentes façons de créer un nouvel objet que vous publiez ici.

CoffeeScript:
C'est en fait la manière la plus claire et traditionnelle de créer votre propre constructeur, mais il a été «optimisé» dans le sens où il a été prêt à configurer les variables de fermeture (facultatives).
Fondamentalement, ce que ce code fait, utilise un IIFE, pour envelopper à la fois la définition du constructeur et les affectations de méthode de proptotype dans leur propre champ privé, qui renvoie une référence au nouveau constructeur. C'est tout simplement propre, simple JS, pas différent de ce que vous pourriez écrire vous-même.

Assommer:
Maintenant, cela m'a jeté un peu, parce que pour moi, au moins, l'extrait que vous fournissez ressemble soit à une partie d'un modèle de module, soit à un constructeur de puissance. Mais puisque vous n'utilisez pas strict mode , en omettant le new serait encore faire pour des situations dangereuses, et comme la fonction entière fonctionne à travers la difficulté de créer une nouvelle instance de DifferentAnimal , il suffit alors de construire un second objet littéral, en attribuant toutes les propriétés de DifferentAnimal à cet objet secondaire, je dirais que vous manquez quelque chose. Parce que la vérité soit racontée, en omettant le dernier return {}; Déclaration ici, ne fera probablement aucune différence du tout. De plus: comme vous le voyez, vous déclarez une méthode ( move ) dans ce qui est, en substance, un constructeur. Cela signifie que chaque instance sera affectée à son propre objet fonction, évolue plutôt que de l'obtenir à partir du prototype.
En bref: regardez attentivement d'où vous avez obtenu cet extrait et vérifiez s'il s'agit de la version complète, car si je ne vois que des arguments à ce sujet.

L'utilisation d'une variable, définie à l'intérieur du constructeur, est simplement: une fermeture, supposons que vos propriétés ont un état initial distinct, déterminé par certains arguments, transmis à ce constructeur:

 function MyConstructor(param) { var paramInit = param/2;//or something this.p = paramInit;//this property can change later on, so: this.reInit = function() {//this method HAS to be inside constructor, every instance needs its own method this.p = paramInit;//var paramInit can't, it's local to this scope }; } var foo = new MyConstructor(10); console.log(foo.p);//5 foo.p = 'hi'; console.log(foo.p);//hi foo.reInit(); console.log(foo.p);//5 console.log(foo.paramInit);//undefined, not available outside object: it's a pseudo-private property 

C'est tout, il est trop, vraiment. Lorsque vous voyez ppl en utilisant var that = this; Ou quelque chose, c'est souvent pour créer une référence à l'objet principal qui est disponible n'importe où, sans avoir à faire face aux maux de tête de this (quelle est this référence? Quelle devrait être la méthode lorsqu'elle s'appliquait à un objet autre que celui initialement Destiné à? Etcetera …)

Colonne vertébrale:
Ici, nous traitons d'un autre cas: l'extension des objets (IE: utiliser des méthodes, des propriétés d'une «classe» (constructeur) existante ou d'une instance particulière) n'est pas la même chose que la simple création d'un objet.
Comme vous le savez, les objets JS peuvent recevoir de nouvelles propriétés à tout moment. Ces propriétés peuvent également être supprimées. Parfois, les propriétés prototypes peuvent être redéfinies sur l'instance elle-même (masquage du comportement prototypique) etc … Donc, tout dépend de ce que vous voulez que l'objet résultant (l'objet nouvellement créé, qui étend l'instance donnée) ressemble à: Voulez-vous prendre toutes les propriétés de l'instance, ou voulez-vous que les deux objets utilisent le même prototype quelque part dans la ligne?
Ces deux choses peuvent être réalisées en utilisant des JS simples, mais ils font un peu plus d'efforts pour vous écrire. Cependant, si vous écrivez, par exemple:

 function Animal(name) { this.name = name; } Animal.prototype.eat= function() { console.log(this.name + ' is eating'); }; 

Cela pourrait être considéré comme l'équivalent de l'écriture:

 var Animal = Object.extend({name:'',eat:function() { console.log(this.name + ' is eating'); }}); 

Beaucoup plus court, mais sans le constructeur.

new vs Object.create
Eh bien, c'est facile: Object.create est beaucoup plus puissant que new : vous pouvez définir des méthodes prototypes, des propriétés (y compris les conditions météorologiques ou non, elles peuvent être énumérées, écrites etc …) au moment où vous devez créer un Objet, au lieu d'avoir à écrire un constructeur et un prototype, ou créer un objet littéral et déranger avec toutes ces lignes Object.defineProperty .
Les inconvénients: Certaines personnes n'utilisent toujours pas de navigateurs compatibles avec ECMA5 (IE8 n'est toujours pas mort). D'après mon expérience: il est devenu difficile de déboguer des scripts importants après un certain temps: bien que j'ai tendance à utiliser les constructeurs de puissance plus que je fais des constructeurs réguliers, je les ai toujours définis au sommet de mon script, avec des éléments distincts, clairs et Des noms assez descriptifs, alors que les littératures d'objet sont des choses que je crée simplement «à la volée» . À l'aide d' Object.create , j'ai remarqué que j'ai tendance à créer des objets qui sont vraiment un peu trop complexes pour être considérés comme des objets réels, comme s'il s'agissait de littéralement objet:

 //fictional example, old: var createSomething = (function() { var internalMethod = function() {//method for new object console.log(this.myProperty || ''); }; return function(basedOn) { var prop, returnVal= {}; returnVal.myProperty = new Date(); returnVal.getCreated = internalMethod;//<--shared by all instances, thx to closure if (!basedOn || !(basedOn instanceof Object)) {//no argument, or argument is not an object: return returnVal; } for (prop in basedOn) {//extend instance, passed as argument if (basedOn.hasOwnProperty(prop) && prop !== '_extends') { returnVal[prop] = basedOn[prop]; } } returnVal._extends = basedOn;//<-- ref as sort-of-prototype return returnVal; }; }()); 

Maintenant, cela est assez détaillé, mais j'ai mon constructeur de base prêt, et je peux l'utiliser pour étendre une instance existante aussi. Il pourrait sembler moins verbal d'écrire simplement:

 var createSomething = Object.create(someObject, {getCreated:function() { console.log(this.myProperty); }, myProperty:new Date()}); 

Mais IMO, cela vous rend plus difficile de garder une trace de l'objet créé (principalement parce que Object.create est une expression et ne sera pas hissé .
Ah, bien, cela est loin d'être un argument concluant bien sûr: les deux ont leurs pro et leur: je préfère utiliser les patches de modules, les fermetures et les constructeurs de puissance, si vous ne le faites pas très bien.

J'espère que cela a permis de nettoyer une chose ou 2 pour vous.

Le premier exemple met la fonction de déplacement dans le prototype qui sera partagé entre toutes les instances d'Animal.

Le deuxième exemple crée une nouvelle fonction de déplacement pour chaque instance animale.

Le troisième exemple génère une classe Animal avec la fonction de déplacement dans le prototype similaire au premier exemple mais avec moins de code. (Dans votre exemple, le nom est également partagé entre toutes les instances, que vous ne voulez probablement pas)

Mettre la fonction dans le prototype rend instancier les animaux plus rapidement, et en raison de la façon dont les moteurs JIT fonctionnent, même l'exécution de la fonction est plus rapide.