Il y a deux modèles pour la mise en œuvre des classes et des instances en JavaScript: le prototypage façon, et la fermeture. Les deux ont des avantages et des inconvénients, et il y a beaucoup d'étendue des variations. De nombreux programmeurs et les bibliothèques ont des approches différentes et classe-la manipulation des fonctions de l'utilitaire de papier au-dessus de certains de la plus laide des parties de la langue.
Le résultat est que, dans la société mixte, vous aurez un méli-mélo de metaclasses, tous se comporter un peu différemment. Ce qui est pire, la plupart JavaScript tutoriel matériel est terrible et sert une sorte d'entre-deux compromis à couvrir toutes les bases, vous laissant très confus. (Probablement l'auteur est également à confusion. JavaScript le modèle d'objet est très différent de la plupart des langages de programmation, et dans de nombreux endroits droite mal conçu.)
Commençons avec le prototype. C'est la plupart du JavaScript natif, vous pouvez obtenir: il y a un minimum de frais généraux code et instanceof va travailler avec les instances de ce type d'objet.
function Shape(x, y) {
this.x= x;
this.y= y;
}
Nous pouvons ajouter des méthodes à l'instance créée par l' new Shape
par écrit à l' prototype
recherche de cette fonction constructeur:
Shape.prototype.toString= function() {
return 'Shape at '+this.x+', '+this.y;
};
Maintenant à la sous-classe, en autant que vous pouvez l'appeler ce que le JavaScript n'sous-classement. Nous le faisons en remplaçant complètement bizarre magie prototype
de la propriété:
function Circle(x, y, r) {
Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
this.r= r;
}
Circle.prototype= new Shape();
avant d'ajouter méthodes:
Circle.prototype.toString= function() {
return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}
Cet exemple fonctionne et vous verrez un code comme dans de nombreux tutoriels. Mais l'homme, new Shape()
, c'est moche: nous instancions la classe de base, même si aucune Forme réelle va être créé. Il arrive à travailler dans ce cas simple, parce que JavaScript est tellement bâclé: il permet à zéro les arguments passés en, dans ce cas x
et y
deviennent undefined
et sont affectés à le prototype de l' this.x
et this.y
. Si la fonction de constructeur faisaient quelque chose de plus compliqué, il tomberait à plat sur son visage.
Donc, ce que nous devons faire est de trouver un moyen de créer un prototype de l'objet qui contient les méthodes et les autres membres de la nous voulons un niveau de classe, sans appel de la classe de base de la fonction constructeur. Pour ce faire, nous allons devoir commencer à écrire helper code. C'est l'approche la plus simple que je connais:
function subclassOf(base) {
_subclassOf.prototype= base.prototype;
return new _subclassOf();
}
function _subclassOf() {};
Cela transfère la classe de base des membres de son prototype d'une nouvelle fonction constructeur qui ne fait rien, puis utilise que constructeur. Maintenant, nous pouvons écrire simplement:
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.prototype= subclassOf(Shape);
au lieu de l' new Shape()
fausseté. Nous avons maintenant un ensemble acceptable de primitives pour construire des classes.
Il y a quelques améliorations et extensions, on peut considérer, en vertu de ce modèle. Pour exemple voici une syntaxe de sucre de version:
Function.prototype.subclass= function(base) {
var c= Function.prototype.subclass.nonconstructor;
c.prototype= base.prototype;
this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};
...
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.subclass(Shape);
La version présente l'inconvénient que la fonction constructeur ne peut pas être héritée, comme elle l'est dans de nombreuses langues. Donc, même si votre sous-classe n'ajoute rien à la construction du processus, il ne faut pas oublier d'appeler le constructeur de base avec tout ce que les arguments de la base voulait. Il peut être légèrement automatisé à l'aide d' apply
, mais encore, vous devez écrire:
function Point() {
Shape.apply(this, arguments);
}
Point.subclass(Shape);
Ainsi, une commune de l'extension est de sortir de l'initialisation des choses dans sa propre fonction plutôt que le constructeur lui-même. Cette fonction peut alors hériter de la base de l'amende juste:
function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!
Maintenant, nous avons juste eu le même constructeur fonction standard pour chaque classe. Peut-être qu'on peut se déplacer que dans son propre fonction d'assistance afin de ne pas avoir garder en tapant, par exemple, au lieu de Function.prototype.subclass
, tourner autour et de laisser la classe de base de la Fonction de cracher le sous-classes:
Function.prototype.makeSubclass= function() {
function Class() {
if ('_init' in this)
this._init.apply(this, arguments);
}
Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};
...
Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
Point= Shape.makeSubclass();
Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
Shape.prototype._init.call(this, x, y);
this.r= r;
};
...qui commence à ressembler un peu plus à l'instar des autres langues, mais avec un peu plus maladroit de la syntaxe. Vous pouvez les saupoudrer de quelques fonctionnalités supplémentaires si vous le souhaitez. Peut-être que vous souhaitez makeSubclass
de prendre et de se rappeler un nom de classe et de fournir une valeur par défaut toString
à l'utiliser. Peut-être que vous voulez faire le constructeur de détecter quand il a accidentellement été appelé sans le new
opérateur (ce qui serait autrement entraînent souvent très ennuyeux de débogage):
Function.prototype.makeSubclass= function() {
function Class() {
if (!(this instanceof Class))
throw('Constructor called without "new"');
...
Peut-être vous voulez passer tous les nouveaux membres et ont makeSubclass
ajouter au prototype, pour vous éviter d'avoir à écrire Class.prototype...
tout à fait aussi beaucoup. Beaucoup de systèmes de classe le faire, par exemple:
Circle= Shape.makeSubclass({
_init: function(x, y, z) {
Shape.prototype._init.call(this, x, y);
this.r= r;
},
...
});
Il y a beaucoup de potentiel de fonctionnalités que vous pourriez envisager souhaitable dans un objet du système et pas vraiment d'accord sur une formule particulière.
La fermeture de la voie, ensuite. Cela évite les problèmes de JavaScript prototype à base d'héritage, en n'utilisant pas d'héritage à tous. Au lieu de cela:
function Shape(x, y) {
var that= this;
this.x= x;
this.y= y;
this.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
}
function Circle(x, y, r) {
var that= this;
Shape.call(this, x, y);
this.r= r;
var _baseToString= this.toString;
this.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+that.r;
};
};
var mycircle= new Circle();
Maintenant, chaque instance unique d' Shape
va avoir sa propre copie de l' toString
méthode (et d'autres méthodes ou d'autres membres de la classe nous ajouter).
La mauvaise chose à propos de tous les cas d'avoir sa propre copie de chaque membre de la classe, c'est que c'est moins efficace. Si vous travaillez avec un grand nombre de sous-classé cas, l'héritage par prototype peut vous servir au mieux. Aussi l'appel d'une méthode de la classe de base est un peu ennuyeux, comme vous pouvez le voir: nous devons nous rappeler en quoi consistait la méthode avant de la sous-classe constructeur a remplacé, ou il est perdu.
[Aussi parce qu'il n'y a pas d'héritage ici, l' instanceof
opérateur ne fonctionne pas, vous devez fournir votre propre mécanisme pour la classe de renifler si vous en avez besoin. Alors que vous pourriez tripoter le prototype objets de la même manière qu'avec l'héritage de prototype, c'est un peu compliqué et pas vraiment la peine juste pour obtenir instanceof
de travail.]
La bonne chose à propos de chaque instance ayant sa propre méthode, c'est que la méthode peut alors être lié à l'instance spécifique qui le possède. Ceci est utile parce que du JavaScript drôle de façon de se lier this
dans les appels de méthode, qui a l'avantage que si vous détachez une méthode à partir de son propriétaire:
var ts= mycircle.toString;
alert(ts());
ensuite, this
à l'intérieur de la méthode ne sera pas en l'occurrence de Cercle comme prévu (il va d'ailleurs être global window
objet, causant d'importants débogage malheur). Dans la réalité cela se produit généralement lorsqu'une méthode est pris et affecté à un setTimeout
, onclick
ou EventListener
en général.
Avec le prototype, vous devez inclure une fermeture pour chaque affectation:
setTimeout(function() {
mycircle.move(1, 1);
}, 1000);
ou, dans le futur (ou maintenant, si vous hack de la Fonction.prototype) vous pouvez aussi le faire avec function.bind()
:
setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);
si votre instances sont faites à la fermeture de la voie, la liaison est réalisé gratuitement par la fermeture de plus de la variable d'instance (généralement appelés that
ou self
, bien que personnellement je conseillerais à l'encontre de ce dernier en tant que self
a déjà un autre, une signification différente en JavaScript). Vous n'obtenez pas les arguments 1, 1
dans l'extrait ci-dessus pour gratuit, donc vous auriez encore besoin d'une autre fermeture ou un bind()
si vous avez besoin de le faire.
Il y a beaucoup de variantes de la méthode de fermeture trop. Vous préférez peut-être omettre this
complètement, la création d'un nouveau that
et de le retourner au lieu d'utiliser l' new
opérateur:
function Shape(x, y) {
var that= {};
that.x= x;
that.y= y;
that.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
return that;
}
function Circle(x, y, r) {
var that= Shape(x, y);
that.r= r;
var _baseToString= that.toString;
that.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+r;
};
return that;
};
var mycircle= Circle(); // you can include `new` if you want but it won't do anything
Ce qui est "bon"? Les deux. Ce qui est "meilleur"? Cela dépend de votre situation. FWIW, j'ai tendance vers le prototypage réel JavaScript héritage quand je suis en train de faire fortement OO genre de choses, et les fermetures simple jetable page effets.
Mais les deux manières sont tout à fait contre-intuitif pour la plupart des programmeurs. Tous deux ont beaucoup de potentiel désordonné variations. Vous pourrez rencontrer les deux (ainsi que de nombreuses entre-deux et généralement cassé les schémas) si vous utilisez d'autres gens du code/les bibliothèques. Il n'y a pas généralement acceptée de répondre. Bienvenue dans le monde merveilleux des objets JavaScript.
[Cela a été une partie 94 de Pourquoi le JavaScript n'Est Pas Mon Préféré Langage de Programmation.]