Fermer

avril 24, 2018

Patterns et meilleures pratiques –


Dans cet article, je vais vous emmener faire un tour des différents styles de création d'objets JavaScript, et comment chacun se construit sur les autres par incréments.

JavaScript a une multitude de styles pour créer des objets, Les nouveaux arrivants et les anciens combattants peuvent se sentir dépassés par les choix et incertains de ce qu'ils devraient utiliser. Mais malgré la variété et la différence de syntaxe de chacun, elles sont plus similaires que vous ne le pensez probablement.

Object Literals

Le premier arrêt de notre visite est la méthode la plus simple de création d'objet JavaScript – l'objet littéral. JavaScript revendique que les objets peuvent être créés "ex nilo", à partir de rien – pas de classe, pas de modèle, pas de prototype – juste pouf! un objet avec méthodes et données:

 var o = {
  x: 42,
  y: 3,14,
  f: function () {},
  g: function () {}
}

Mais il y a un inconvénient. Si nous devons créer le même type d'objet à d'autres endroits, nous finirons par copier-coller les méthodes, les données et l'initialisation de l'objet. Nous avons besoin d'un moyen de créer non seulement un objet, mais une famille d'objets.

Factory Functions

Le prochain arrêt de notre tour de création d'objet JavaScript est la fonction factory. C'est le moyen le plus simple de créer une famille d'objets partageant la même structure, l'interface et la même implémentation. Plutôt que de créer directement un littéral d'objet, nous renvoyons plutôt un littéral d'objet à partir d'une fonction. De cette façon, si nous devons créer le même type d'objet plusieurs fois ou à plusieurs endroits, nous avons seulement besoin d'invoquer une fonction:

 function thing () {
  revenir {
    x: 42,
    y: 3,14,
    f: function () {},
    g: function () {}
  }
}

var o = chose ();

Mais il y a un inconvénient. Cette approche de la création d'objet JavaScript peut provoquer une surcharge de mémoire, car chaque objet contient sa propre copie unique de chaque fonction. Idéalement, nous voulons que chaque objet ne partage qu'une seule copie de ses fonctions

Prototype Chains

JavaScript nous donne un mécanisme intégré pour partager des données entre les objets, appelé la chaîne prototype . Lorsque nous accédons à une propriété sur un objet, elle peut répondre à cette demande en la déléguant à un autre objet. Nous pouvons l'utiliser et modifier notre fonction factory de sorte que chaque objet créé contienne uniquement les données propres à cet objet particulier et déléguer toutes les autres demandes de propriété à un seul objet partagé:

 var thingPrototype = {
  f: function () {},
  g: function () {}
}

chose de fonction () {
  var o = Object.create (thingPrototype);

  o.x = 42;
  o.y = 3,14;

  retour o;
}

var o = chose ();

En fait, c'est un modèle si commun que le langage a un support intégré pour cela. Nous n'avons pas besoin de créer notre propre objet partagé (l'objet prototype). Au lieu de cela, un objet prototype est créé pour nous automatiquement à côté de chaque fonction, et nous pouvons y mettre nos données partagées:

 thing.prototype.f = function () {};
thing.prototype.g = function () {};

chose de fonction () {
  var o = Object.create (chose.prototype);

  o.x = 42;
  o.y = 3,14;

  retour o;
}

var o = chose ();

Mais il y a un inconvénient. Cela va entraîner une certaine répétition. Les première et dernière lignes de la fonction vont être répétées presque textuellement dans chaque fonction d'usine de délégation à prototype

Classes ES5

On peut isoler les lignes répétitives en les déplaçant dans leur propre fonction. Cette fonction créerait un objet qui délègue au prototype d'une autre fonction arbitraire, puis invoquerait cette fonction avec l'objet nouvellement créé comme argument, et retournerait finalement l'objet:

 function create (fn) {
  var o = Object.create (fn.prototype);

  fn.call (o);

  retour o;
}

// ...

Thing.prototype.f = function () {};
Thing.prototype.g = fonction () {};

Fonction Chose () {
  this.x = 42;
  this.y = 3,14;
}

var o = créer (Chose);

En fait, c'est aussi un modèle si commun que le langage a un support intégré pour cela. La fonction create que nous avons définie est en fait une version rudimentaire du nouveau mot-clé et nous pouvons remplacer créer avec nouveau :

 Thing.prototype.f = function () {};
Thing.prototype.g = fonction () {};

Fonction Chose () {
  this.x = 42;
  this.y = 3,14;
}

var o = new Chose ();

Nous sommes maintenant arrivés à ce que nous appelons communément "classes ES5". Ce sont des fonctions de création d'objet qui délèguent des données partagées à un objet prototype et s'appuient sur le nouveau mot-clé pour gérer la logique répétitive.

Mais il y a un inconvénient. Il est verbeux et moche, et l'implémentation de l'héritage est encore plus verbeuse et moche.

ES6 Classes

Un ajout relativement récent à JavaScript est les classes ES6, qui offrent une syntaxe nettement plus propre pour faire la même chose:

 {
  constructeur () {
    this.x = 42;
    this.y = 3,14;
  }

  F() {}
  g() {}
}

const o = nouvelle chose ();

Comparaison

Au cours des années, nous JavaScripters avons eu une relation continue avec la chaîne du prototype, et aujourd'hui les deux styles les plus communs que vous êtes susceptible de rencontrer sont la syntaxe de classe, qui repose fortement sur le chaîne de prototypes, et la syntaxe de la fonction d'usine, qui ne dépend généralement pas de la chaîne du prototype. Les deux styles diffèrent – mais seulement légèrement – des performances et des fonctionnalités.

Performance

Les moteurs JavaScript sont tellement optimisés aujourd'hui qu'il est presque impossible de regarder notre code et de raisonner sur ce qui sera le plus rapide. La mesure est cruciale. Pourtant, même la mesure peut parfois nous manquer. Généralement, un moteur JavaScript mis à jour est publié toutes les six semaines, parfois avec des changements significatifs de performance, et toutes les mesures que nous avions prises précédemment, et toutes les décisions que nous avons prises en fonction de ces mesures, sortent par la fenêtre. Donc, ma règle empirique a été de privilégier la syntaxe la plus officielle et la plus répandue, avec la présomption qu'elle sera la plus surveillée et la plus performante la plupart du temps . En ce moment, c'est la syntaxe de classe, et comme j'écris ceci, la syntaxe de classe est environ 3x plus rapide qu'une fonction d'usine retournant un littéral.

Features

Les différences de fonctions entre les classes et les fonctions d'usine évaporées avec ES6 . Aujourd'hui, les fonctions d'usine et les classes peuvent appliquer des fonctions de data-factory véritablement privées avec des fermetures et des classes avec des cartes faibles. Les deux peuvent réaliser des fonctions d'usine à héritages multiples en mélangeant d'autres propriétés dans leur propre objet, et en classes aussi en mélangeant d'autres propriétés dans leur prototype, ou avec des usines de classe, ou avec des proxies. Les fonctions d'usine et les classes peuvent retourner n'importe quel objet arbitraire si besoin est. Et tous les deux offrent une syntaxe simple.

Conclusion

Tout bien considéré, ma préférence pour la création d'un objet JavaScript est d'utiliser la syntaxe de la classe. C'est standard, c'est simple et propre, c'est rapide, et il fournit toutes les fonctionnalités que seules les usines pouvaient livrer

Cet article a été révisé par Tim Severien et Sebastian Seitz . Merci à tous les pairs évaluateurs de SitePoint pour avoir rendu le contenu de SitePoint le meilleur possible




Source link