Comment créer «correctement» un objet personnalisé en JavaScript?

471

Je me demande quelle est la meilleure façon de créer un objet JavaScript qui possède des propriétés et des méthodes.

J'ai vu des exemples où la personne a utilisé var self = thispuis utilise self.dans toutes les fonctions pour s'assurer que la portée est toujours correcte.

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

Quelqu'un peut-il me donner un bon exemple d'un objet JavaScript avec certaines propriétés et méthodes?

Michael Stum
la source
13
Il n'y a pas de "meilleur" moyen.
Triptyque
N'est-ce pas selfun mot réservé? Sinon, cela devrait l'être; puisqu'il selfs'agit d'une variable prédéfinie faisant référence à la fenêtre en cours. self === window
Shaz
2
@Shaz: ce n'est pas un mot réservé pas plus que d'autres propriétés de windowdans le modèle d'objet du navigateur comme documentou frames; vous pouvez certainement réutiliser l'identifiant comme nom de variable. Bien que, oui, stylistiquement, je préfère var that= thiséviter toute confusion possible. Même si window.selfc'est finalement inutile donc il n'y a rarement aucune raison d'y toucher.
bobince
7
Lorsque JS est réduit, l'affectation thisà une variable locale (par exemple self) réduit la taille des fichiers.
Patrick Fisher
Nouveau lien Classjs
Nikola

Réponses:

889

Il existe deux modèles pour implémenter des classes et des instances en JavaScript: la méthode de prototypage et la méthode de fermeture. Les deux ont des avantages et des inconvénients, et il existe de nombreuses variantes étendues. De nombreux programmeurs et bibliothèques ont des approches et des fonctions utilitaires de gestion de classe différentes pour imprimer sur certaines des parties les plus laides du langage.

Le résultat est qu'en compagnie mixte, vous aurez un méli-mélo de métaclasses, toutes se comportant légèrement différemment. Pire encore, la plupart des didacticiels JavaScript sont terribles et servent de compromis intermédiaires pour couvrir toutes les bases, vous laissant très confus. (L'auteur est probablement aussi confus. Le modèle d'objet de JavaScript est très différent de la plupart des langages de programmation, et dans de nombreux endroits, il est mal conçu.)

Commençons par la manière prototype . C'est le plus natif de JavaScript que vous pouvez obtenir: il y a un minimum de code de surcharge et instanceof fonctionnera 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 en les new Shapeécrivant dans la prototyperecherche de cette fonction constructeur:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Maintenant, pour le sous-classer, dans la mesure où vous pouvez appeler ce que JavaScript fait de la sous-classe. Nous le faisons en remplaçant complètement cette prototypepropriété magique étrange :

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'y ajouter des méthodes:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Cet exemple fonctionnera et vous verrez du code similaire dans de nombreux didacticiels. Mais l'homme, c'est new Shape()moche: nous instancions la classe de base même si aucune forme réelle n'est à créer. Il se trouve que cela fonctionne dans ce cas simple parce que JavaScript est tellement bâclé: il permet de passer zéro arguments, auquel cas xet ydevenir undefinedet sont assignés aux prototypes this.xet this.y. Si la fonction constructeur faisait quelque chose de plus compliqué, elle tomberait à plat sur son visage.

Donc, ce que nous devons faire, c'est trouver un moyen de créer un objet prototype qui contient les méthodes et autres membres que nous voulons au niveau de la classe, sans appeler la fonction constructeur de la classe de base. Pour ce faire, nous allons devoir commencer à écrire du code d'aide. C'est l'approche la plus simple que je connaisse:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

Cela transfère les membres de la classe de base dans son prototype vers une nouvelle fonction constructeur qui ne fait rien, puis utilise ce constructeur. Maintenant, nous pouvons écrire simplement:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

à la place du new Shape() injustice. Nous avons maintenant un ensemble acceptable de primitives pour les classes construites.

Il y a quelques améliorations et extensions que nous pouvons considérer sous ce modèle. Par exemple, voici une version syntaxique de sucre:

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);

L'une ou l'autre version présente l'inconvénient que la fonction constructeur ne peut pas être héritée, comme c'est le cas dans de nombreux langages. Ainsi, même si votre sous-classe n'ajoute rien au processus de construction, elle doit se rappeler d'appeler le constructeur de base avec les arguments souhaités par la base. Cela peut être légèrement automatisé en utilisant apply, mais vous devez toujours écrire:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Ainsi, une extension courante consiste à décomposer les éléments d'initialisation en sa propre fonction plutôt que le constructeur lui-même. Cette fonction peut alors très bien hériter de la base:

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!

Nous avons maintenant le même passe-partout de fonction constructeur pour chaque classe. Peut-être pouvons-nous déplacer cela dans sa propre fonction d'assistance afin que nous n'ayons pas à le taper, par exemple au lieu de le Function.prototype.subclassretourner et laisser la fonction Function de la classe de base cracher des 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 à d'autres langages, mais avec une syntaxe légèrement plus maladroite. Vous pouvez saupoudrer quelques fonctionnalités supplémentaires si vous le souhaitez. Vous souhaitez peut-être makeSubclassprendre et mémoriser un nom de classe et fournir une valeur toStringpar défaut en l' utilisant. Peut-être que vous voulez que le constructeur détecte quand il a été accidentellement appelé sans l' newopérateur (ce qui entraînerait souvent un débogage très ennuyeux):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Peut-être que vous voulez passer tous les nouveaux membres et les makeSubclassajouter au prototype, pour vous éviter d'avoir à écrire Class.prototype...beaucoup. De nombreux systèmes de classe le font, par exemple:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

Il existe de nombreuses fonctionnalités potentielles que vous pourriez considérer comme souhaitables dans un système d'objets et personne n'est vraiment d'accord sur une formule particulière.


La fermeture , alors. Cela évite les problèmes d'héritage basé sur un prototype de JavaScript, en n'utilisant pas du tout l'héritage. Au lieu:

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();

Désormais, chaque instance de Shapeaura sa propre copie du fichiertoString méthode (et toute autre méthode ou tout autre membre de classe que nous ajouterons).

La mauvaise chose à propos de chaque instance ayant sa propre copie de chaque membre de la classe est qu'elle est moins efficace. Si vous avez affaire à un grand nombre d'instances sous-classées, l'héritage prototypique peut mieux vous servir. Appeler également une méthode de la classe de base est un peu ennuyeux comme vous pouvez le voir: nous devons nous souvenir de ce qu'était la méthode avant que le constructeur de la sous-classe ne l'écrase, sinon elle se perd.

[Aussi parce qu'il n'y a pas d'héritage ici, l' instanceofopérateur ne fonctionnera pas; vous devrez fournir votre propre mécanisme de reniflement de classe si vous en avez besoin. Bien que vous puissiez manipuler les objets prototypes de la même manière qu'avec l'héritage de prototypes, c'est un peu délicat et cela ne vaut pas vraiment la peine de se mettre au instanceoftravail.]

La bonne chose à propos de chaque instance ayant sa propre méthode est que la méthode peut alors être liée à l'instance spécifique qui la possède. Ceci est utile en raison de la façon étrange de liaison de JavaScript thisdans les appels de méthode, qui a le résultat que si vous détachez une méthode de son propriétaire:

var ts= mycircle.toString;
alert(ts());

puis thisà l'intérieur de la méthode ne sera pas l'instance Circle comme prévu (ce sera en fait l' windowobjet global , provoquant un malheur de débogage généralisé). En réalité, cela se produit généralement lorsqu'une méthode est prise et affectée à un setTimeout, onclickouEventListener en général.

Avec la méthode prototype, vous devez inclure une fermeture pour chaque mission de ce type:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

ou, à l'avenir (ou maintenant si vous piratez Function.prototype), vous pouvez également le faire avec function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

si vos instances sont effectuées de la manière de fermeture, la liaison est effectuée gratuitement par la fermeture sur la variable d'instance (généralement appelée thatou self, bien que personnellement je déconseille cette dernière car elle a selfdéjà une autre signification différente en JavaScript). 1, 1Cependant, vous ne recevez pas les arguments dans l'extrait ci-dessus gratuitement, vous aurez donc toujours besoin d'une autre fermeture ou d'un bind()si vous avez besoin de le faire.

Il existe également de nombreuses variantes de la méthode de fermeture. Vous pouvez préférer omettre thiscomplètement, en créer un nouveau thatet le renvoyer au lieu d'utiliser l' newopé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

Quelle voie est «appropriée»? Tous les deux. Quel est le «meilleur»? Cela dépend de votre situation. FWIW J'ai tendance à prototyper pour un véritable héritage JavaScript lorsque je fais fortement des trucs OO, et des fermetures pour de simples effets de page jetables.

Mais les deux façons sont assez contre-intuitives pour la plupart des programmeurs. Les deux ont de nombreuses variations potentiellement désordonnées. Vous rencontrerez les deux (ainsi que de nombreux schémas intermédiaires et généralement cassés) si vous utilisez le code / les bibliothèques d'autres personnes. Il n'y a pas de réponse généralement acceptée. Bienvenue dans le monde merveilleux des objets JavaScript.

[Cela fait partie 94 de Pourquoi JavaScript n'est pas mon langage de programmation préféré.]

bobince
la source
13
Très belle transition progressive de la définition de "classe" à l'instanciation d'objet. Et belle touche sur le contournement new.
Crescent Fresh
8
Il semble que JavaScript ne soit pas votre langue préférée car vous voulez l'utiliser comme s'il avait des classes.
Jonathan Feinberg
59
Bien sûr que je le fais, tout le monde aussi: le modèle de classe et d'instance est le plus naturel pour la majorité des problèmes courants auxquels les programmeurs sont confrontés aujourd'hui. Je suis d'accord que, sur une base théorique, l'héritage basé sur un prototype peut potentiellement offrir une manière de travailler plus flexible, mais JavaScript ne tient absolument pas cette promesse. Son système de fonction constructeur maladroit nous donne le pire des deux mondes, rendant l'héritage de classe difficile tout en n'offrant aucun des prototypes de flexibilité ou de simplicité que pourraient offrir. Bref, c'est du caca.
bobince
4
Bob, je pense que c'est une réponse impressionnante - je me débat avec ces deux modèles depuis un moment et je pense que vous avez codé quelque chose de plus concis qu'un Resig et expliqué avec plus de perspicacité qu'un Crockford. Je ne pense pas à des éloges plus élevés ...
James Westgate
4
Il m'a toujours semblé que représenter graphiquement les paradigmes d'héritage classiques sur des langages prototypiques comme le javascript est une cheville carrée et un trou rond. Y a-t-il des moments où cela est vraiment nécessaire ou est-ce juste une façon pour les gens de pointer la langue comme ils le souhaitent plutôt que d'utiliser simplement la langue pour ce qu'elle est?
slf
90

J'utilise ce modèle assez fréquemment - j'ai trouvé qu'il me donne une assez grande flexibilité lorsque j'en ai besoin. En cours d'utilisation, il est assez similaire aux classes de style Java.

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;
}();

Celui-ci utilise une fonction anonyme appelée lors de la création, renvoyant une nouvelle fonction constructeur. Étant donné que la fonction anonyme n'est appelée qu'une seule fois, vous pouvez y créer des variables statiques privées (elles sont à l'intérieur de la fermeture, visibles par les autres membres de la classe). La fonction constructeur est fondamentalement un objet Javascript standard - vous définissez des attributs privés à l'intérieur et des attributs publics sont attachés à la thisvariable.

Fondamentalement, cette approche combine l'approche Crockfordian avec des objets Javascript standard pour créer une classe plus puissante.

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
ShZ
la source
4
Cela semble intéressant, car il est plutôt proche de mon "home-turf" qui est C #. Je pense aussi que je commence à comprendre pourquoi privateStaticVariable est vraiment privé (car il est défini dans le cadre d'une fonction et maintenu en vie tant qu'il y a des références?)
Michael Stum
Puisqu'il n'utilise pas, thisdoit-il encore être instancié avec new?
Jordan Parmer
En fait, this est utilisé dans la constructorfonction, qui devient Foodans l'exemple.
ShZ
4
Le problème ici est que chaque objet obtient sa propre copie de toutes les fonctions privées et publiques.
virtualnobi
2
@virtualnobi: Ce modèle ne vous empêche pas d'écrire des méthodes de protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin
25

Douglas Crockford discute longuement de ce sujet dans The Good Parts . Il recommande d'éviter au nouvel opérateur de créer de nouveaux objets. Au lieu de cela, il propose de créer des constructeurs personnalisés. 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ée pour construire des objets avec le nouvel opérateur. Par convention, les fonctions destinées à être utilisées comme constructeurs commencent par une majuscule. Vous voyez souvent des choses comme:

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

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

Si vous oubliez d'utiliser le nouvel opérateur lors de l' instanciation d' un nouvel objet, ce que vous obtenez est un appel de fonction ordinaire, et c'est lié à l'objet global plutôt que le nouvel objet.

Diego Pino
la source
5
Est-ce moi ou je pense que Crockford n'a aucun sens avec son dénigrement du nouvel opérateur?
meder omuraliev
3
@meder: Pas seulement vous. Du moins, je pense qu'il n'y a rien de mal avec le nouvel opérateur. Et il y a un implicite newde var that = {};toute façon.
Tim Down
17
Crockford est un vieil homme grincheux et je suis en désaccord avec lui sur beaucoup de choses, mais il promeut au moins de jeter un regard critique sur JavaScript et cela vaut la peine d'écouter ce qu'il a à dire.
bobince
2
@bobince: D'accord. Ses écrits sur les fermetures m'ont ouvert les yeux sur beaucoup de choses il y a environ 5 ans, et il encourage une approche réfléchie.
Tim Down
20
Je suis d'accord avec Crockford. Le problème avec le nouvel opérateur est que JavaScript rendra le contexte de "ceci" très différent de celui utilisé pour appeler une fonction. Malgré la convention de cas appropriée, des problèmes surviennent sur des bases de code plus importantes car les développeurs oublient d'utiliser new, oublient de capitaliser, etc. Pour être pragmatique, vous pouvez faire tout ce que vous devez faire sans le nouveau mot-clé - alors pourquoi l'utiliser et introduire plus de points de défaillance dans le code? JS est un langage prototypique, non basé sur une classe. Alors pourquoi voulons-nous qu'il agisse comme un langage tapé de façon statique? Certainement pas.
Joshua Ramirez
13

Pour continuer hors de la réponse de Bobince

Dans es6, vous pouvez maintenant réellement créer un class

Alors maintenant, vous pouvez faire:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Alors, étendez-vous à un cercle (comme dans l'autre réponse), vous pouvez faire:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

Finit un peu plus propre dans es6 et un peu plus facile à lire.


En voici un bon exemple en action:

Naftali aka Neal
la source
6

Vous pouvez également le faire de cette façon, en utilisant des structures:

function createCounter () {
    var count = 0;

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

Alors :

var counter1 = createCounter();
counter1.increaseBy(4);
Eino Gourdin
la source
6
Je n'aime pas ça parce que les espaces blancs sont importants. Le bouclé après le retour doit être sur la même ligne pour la compatibilité entre les navigateurs.
geowa4
5

Une autre façon serait http://jsfiddle.net/nnUY4/ (je ne sais pas si ce type de gestion de la création d'objets et des fonctions de révélation suit un modèle spécifique)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"
Fluchtpunkt
la source
4

Quand on utilise l'astuce de fermer sur "this" lors d'une invocation de constructeur, c'est pour écrire une fonction qui peut être utilisée comme rappel par un autre objet qui ne veut pas invoquer une méthode sur un objet. Ce n'est pas lié à "rendre la portée correcte".

Voici un objet JavaScript vanille:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Vous pourriez tirer beaucoup de la lecture de ce que Douglas Crockford a à dire sur JavaScript. John Resig est également brillant. Bonne chance!

Jonathan Feinberg
la source
1
Euh, la fermeture thisa tout à voir avec "rendre la portée correcte".
Roatin Marth
3
Jonathan a raison. La portée d'une fonction js est celle que vous concevez. Le self = cette astuce est un moyen de le lier à une instance particulière afin qu'il ne change pas lorsqu'il est appelé dans un autre contexte. Mais parfois, c'est ce que vous voulez réellement. Dépend du contexte.
Marco
Je pense que vous dites tous la même chose en fait. self=thisbien qu'il ne force pas thisà persister, permet facilement un cadrage "correct" via une fermeture.
Crescent Fresh
2
La raison pour laquelle vous faites cela = c'est pour donner aux fonctions imbriquées l'accès à la portée de ceci tel qu'il existe dans la fonction constructeur. Lorsque les fonctions imbriquées se trouvent à l'intérieur des fonctions constructeur, leur portée "this" revient à la portée globale.
Joshua Ramirez
4

Closureest polyvalent. bobince a bien résumé les approches prototype vs fermeture lors de la création d'objets. Cependant, vous pouvez imiter certains aspects de l' OOPutilisation de la fermeture d'une manière de programmation fonctionnelle. N'oubliez pas que les fonctions sont des objets en JavaScript ; alors utilisez la fonction comme objet d'une manière différente.

Voici un exemple de 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 quelque temps, je suis tombé sur l'article de Mozilla sur la fermeture. Voici ce qui me saute aux yeux: "Une fermeture vous permet d'associer certaines données (l'environnement) à une fonction qui opère sur ces données. Cela a des parallèles évidents avec la programmation orientée objet, où les objets nous permettent d'associer certaines données (les propriétés de l'objet ) avec une ou plusieurs méthodes ". C'était la toute première fois que je lisais un parallélisme entre la fermeture et la POO classique sans référence au prototype.

Comment?

Supposons que vous souhaitiez calculer la TVA de certains articles. La TVA est susceptible de rester stable pendant 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 transmettez une valeur de TVA à votre constructeur et votre méthode de calcul peut l'utiliser via la fermeture . Maintenant, au lieu d'utiliser une classe / constructeur, passez votre TVA comme argument dans une fonction. Parce que la seule chose qui vous intéresse est le calcul lui-même, retourne une nouvelle fonction, qui est la méthode de calcul:

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

Dans votre projet, identifiez les valeurs de niveau supérieur qui sont un bon candidat de la TVA à calculer. En règle générale, chaque fois que vous transmettez les mêmes arguments, il existe un moyen de l'améliorer en utilisant la fermeture. Pas besoin de créer des objets traditionnels.

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

Roland
la source
3

Création d'un objet

La façon la plus simple de créer un objet en JavaScript est d'utiliser la syntaxe suivante:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Cela fonctionne très bien pour stocker des données de manière structurée.

Pour les cas d'utilisation plus complexes, cependant, il est souvent préférable de créer des instances de fonctions:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Cela vous permet de créer plusieurs objets qui partagent le même «plan directeur», semblable à la façon dont vous utilisez les classes, par exemple. Java.

Cependant, cela peut être fait plus efficacement en utilisant un prototype.

Chaque fois que différentes instances d'une fonction partagent les mêmes méthodes ou propriétés, vous pouvez les déplacer vers le prototype de cet objet. De cette façon, chaque instance d'une fonction a accès à cette méthode ou propriété, mais elle n'a pas besoin d'être dupliquée pour chaque instance.

Dans notre cas, il est logique de déplacer la méthode fvers le prototype:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Héritage

Un moyen simple mais efficace de faire l'héritage en JavaScript, consiste à utiliser la double ligne suivante:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Cela revient à faire ceci:

B.prototype = new A();

La principale différence entre les deux est que le constructeur de An'est pas exécuté lors de l'utilisation Object.create, ce qui est plus intuitif et plus similaire à l'héritage basé sur une classe.

Vous pouvez toujours choisir d'exécuter éventuellement le constructeur de Alors de la création d'une nouvelle instance de Ben l'ajoutant au constructeur de B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Si vous souhaitez passer tous les arguments de Bà A, vous pouvez également utiliser Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Si vous souhaitez mélanger un autre objet dans la chaîne constructeur de B, vous pouvez combiner Object.createavec Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Démo

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


Remarque

Object.create peut être utilisé en toute sécurité dans tous les navigateurs modernes, y compris IE9 +. Object.assignne fonctionne dans aucune version d'IE ni dans certains navigateurs mobiles. Il est recommandé de polyfill Object.create et / ou Object.assignsi vous souhaitez les utiliser et prendre en charge les navigateurs qui ne les implémentent pas.

Vous pouvez trouver un polyfill pour Object.create ici et un pour Object.assign ici .

John Slegers
la source
0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);
adeola olanrewaju
la source
4
Qu'est-ce que cela ajoute aux nombreuses réponses détaillées déjà proposées?
blm
J'aime cette réponse car elle est concise et montre les trois parties de l'implémentation: 1) Définir l'objet, 2) Instancier une instance de l'objet, 3) Utiliser l'instance - elle montre tout en un coup d'œil au lieu d'avoir l'analyse à travers toutes les réponses verbeuses ci-dessus (qui, bien sûr, sont toutes des réponses extrêmement bonnes avec tous les détails pertinents que l'on voudrait) - une sorte de résumé simple ici
G-Man
0

En plus de la réponse acceptée de 2009. Si vous pouvez cibler des navigateurs modernes, vous pouvez utiliser Object.defineProperty .

La méthode Object.defineProperty () définit une nouvelle propriété directement sur un objet ou modifie une propriété existante sur un objet et renvoie l'objet. Source: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Pour voir une liste de compatibilité de bureau et mobile, consultez la liste de compatibilité du navigateur de Mozilla . Oui, IE9 + le prend en charge ainsi que Safari mobile.

Alex Nolasco
la source
0

Vous pouvez également essayer ceci

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();
G Naga Subrahmanyam
la source
0
Un modèle qui me sert bien
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

Tout d'abord, vous pouvez modifier votre préférence en ajoutant des méthodes à l'instance au lieu de l' prototypeobjet du constructeur . Je déclare presque toujours des méthodes à l'intérieur du constructeur car j'utilise le détournement de constructeur très souvent le à des fins concernant l'héritage et les décorateurs.

Voici comment je décide où les déclarations sont écrites:

  • Ne déclarez jamais une méthode directement sur l'objet contextuel ( this)
  • Laissons les vardéclarations l'emporter surfunction déclarations
  • Laissez les primitives prendre le pas sur les objets ( {}et[] )
  • Laissons les publicdéclarations l'emporter surprivate déclarations
  • Préférez Function.prototype.bindplus thus, self, vm,etc
  • Évitez de déclarer une classe dans une autre classe, sauf si:
    • Il devrait être évident que les deux sont inséparables
    • La classe Inner implémente le modèle de commande
    • La classe Inner implémente le modèle Singleton
    • La classe Inner implémente le modèle d'état
    • La classe intérieure met en œuvre un autre modèle de conception qui garantit cette
  • Revenez toujours thisdepuis la portée lexicale de l'espace de fermeture.

Voici pourquoi ces aides:

Détournement de constructeur
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Conception du modèle
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Modèles de conception
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Comme vous pouvez le voir, je n'en ai vraiment pas besoin thuscar je préfère Function.prototype.bind(ou .callou .apply) plusthus . Dans notre Singletonclasse, nous ne le nommons même pas thuscar il INSTANCEtransmet plus d'informations. Pour Model, nous revenons thisafin de pouvoir invoquer le constructeur en utilisant .callpour renvoyer l'instance que nous lui avons passée. De façon redondante, nous l'avons affecté à la variable updated, bien qu'il soit utile dans d'autres scénarios.

Parallèlement, je préfère construire des objets littéraux en utilisant le new mot clé plutôt que {crochets}:

Préféré
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Pas préféré
var klass = Super.apply({
    override: x
});

Comme vous pouvez le voir, ce dernier n'a pas la possibilité de remplacer la propriété "override" de sa Superclasse.

Si j'ajoute des méthodes à l' prototypeobjet de la classe , je préfère un littéral d'objet - avec ou sans utiliser lenew mot clé:

Préféré
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
Pas préféré
Klass.prototype.method = function m() {...};
Cody
la source
0

Je voudrais mentionner que nous pouvons utiliser un titre ou une chaîne pour déclarer un objet.
Il existe différentes manières d'appeler chaque type d'entre eux. Voir ci-dessous:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));

Chetabahana
la source
-1

Fondamentalement, il n'y a pas de concept de classe dans JS, nous utilisons donc la fonction comme un constructeur de classe qui est pertinent avec les modèles de conception existants.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

Jusqu'à présent, JS n'a aucune idée que vous souhaitez créer un objet, alors voici le nouveau mot-clé.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref: JS professionnel pour les développeurs web - Nik Z

Airwind711
la source
Downvote accepté: avec une raison valable aurait été plus informatif et aurait été l'occasion de s'améliorer.
Airwind711
Il y a le concept d'un classdans JS, comme vous l'avez mentionné dans votre rubrique en utilisant le functionmot - clé. Ce n'est pas un modèle de conception mais une caractéristique intentionnelle de la langue. Je ne vous ai pas déçu à ce sujet, mais il semble que quelqu'un d'autre l'ait fait à cause de la justesse et de la non-pertinence de la question. J'espère que cette rétroaction vous aidera.
Cody