480 votes

Comment "bien" créer un objet personnalisé en JavaScript?

Je suis encore à essayer de comprendre le JavaScript, et je m'interroge sur ce que le meilleur moyen est de créer un objet qui a des propriétés et des méthodes.

J'ai vu des exemples où la personne a utilisé "var self = this" et utilise ensuite "soi". dans toutes les fonctions assurez-vous que le champ d'application est toujours correct.

Puis j'ai vu des exemples d'utilisation .prototype pour ajouter des propriétés, tandis que d'autres le font en ligne.

Quelqu'un peut me donner un bon exemple d'un objet JavaScript avec des Propriétés et des Méthodes?

Merci!

896voto

bobince Points 270740

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.]

90voto

ShZ Points 3431

J'utilise ce modèle assez souvent - j'ai trouvé ce qu'il me donne un assez énorme quantité de flexibilité quand j'en ai besoin. Il est assez similaire à Java-classes de style.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Il utilise une fonction anonyme qui est appelée lors de la création, de retour d'une nouvelle fonction constructeur. Parce que la fonction anonyme est appelée qu'une seule fois, vous pouvez créer privée variables statiques (ils sont à l'intérieur de la clôture, visible par les autres membres de la classe). Le constructeur fonction est essentiellement une norme Javascript object - vous définir privé des attributs à l'intérieur et les attributs sont attachés à l' this variable.

Fondamentalement, cette approche combine la Crockfordian approche standard des objets en Javascript pour créer un son plus puissant de la classe.

Vous pouvez l'utiliser comme vous le feriez pour tout autre objet Javascript:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

25voto

Diego Pino Points 2849

Douglas Crockford traite de ce sujet largement dans Les Bonnes Parties. Il recommande d'éviter le nouvel opérateur pour créer de nouveaux objets. Au lieu de cela, il propose de créer sur mesure des constructeurs. Par exemple:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

En Javascript une fonction est un objet, et peut être utilisé pour construire des objets de concert avec le nouvel opérateur. Par convention, les fonctions destinées à être utilisées en tant que constructeurs de commencer avec une lettre majuscule. Vous voyez souvent des choses comme:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

Dans le cas où vous oubliez d'utiliser le nouvel opérateur, tandis que l'instanciation d'un nouvel objet, ce que vous obtenez est un banal appel de fonction, et cela est lié à l'objet global à la place pour le nouvel objet.

6voto

Eino Gourdin Points 440

Vous pouvez également le faire de cette façon, à l'aide de structures :

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Alors :

var counter1 = createCounter();
counter1.increaseBy(4);

4voto

roland Points 1433

Closure est polyvalent. bobince a bien résumé le prototype contre la fermeture des approches lors de la création d'objets. Cependant, vous pouvez imiter certains aspects de l' OOP à l'aide de la fermeture d'une programmation fonctionnelle. Rappelez-vous les fonctions sont des objets en JavaScript; utilisez donc la fonction en tant qu'objet dans une manière différente.

Voici un exemple de la fermeture:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Il y a un moment je suis tombé sur le Mozilla article sur la Fermeture. Voici ce que saut à mes yeux: "Une fermeture permet d'associer des données (l'environnement) avec une fonction qui fonctionne sur la base de données. Cela a des parallèles évidents à la programmation orientée objet, où les objets nous permettent d'associer des données (propriétés de l'objet) avec une ou plusieurs méthodes". C'était la première fois que je lis un parallélisme entre la clôture et la classique de la programmation orientée objet avec aucune référence à un prototype.

Comment?

Supposons que vous souhaitez calculer la TVA de certains éléments. La TVA est susceptible de rester stable au cours de la durée de vie d'une application. Une façon de le faire en POO (pseudo-code):

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

Fondamentalement, vous passer à la TVA valeur dans votre constructeur et votre de calculer la méthode peut fonctionner sur elle par la fermeture. Maintenant, au lieu d'utiliser une classe/un constructeur, un passage de la TVA comme un argument dans une fonction. Parce que le seul truc qui vous intéresse est le calcul lui-même, renvoie une nouvelle fonction, qui est la méthode calculate:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

Dans votre projet d'identifier de haut niveau des valeurs qui sont de bons candidats de ce que la TVA est à utiliser pour le calcul. En règle générale, chaque fois que vous passez les mêmes arguments sur et sur, il y a un moyen de l'améliorer à l'aide de la fermeture. Pas besoin de créer des objets traditionnels.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X