174 votes

Pourquoi l'extension des objets natifs est-elle une mauvaise pratique ?

Tous les leaders d'opinion de JS disent que l'extension des objets natifs est une mauvaise pratique. Mais pourquoi ? Y a-t-il un impact sur les performances ? Ont-ils peur que quelqu'un fasse les choses "à l'envers", et ajoute des types énumérables aux objets natifs ? Object en détruisant pratiquement toutes les boucles sur n'importe quel objet ?

Prenez TJ Holowaychuk 's devrait.js par exemple. Il ajoute un simple getter a Object et tout fonctionne bien ( fuente ).

Object.defineProperty(Object.prototype, 'should', {
  set: function(){},
  get: function(){
    return new Assertion(Object(this).valueOf());
  },
  configurable: true
});

Cela a vraiment du sens. Par exemple, on pourrait étendre Array .

Array.defineProperty(Array.prototype, "remove", {
  set: function(){},
  get: function(){
    return removeArrayElement.bind(this);
  }
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);

Existe-t-il des arguments contre l'extension des types natifs ?

8voto

Eugen Wesseloh Points 111

Une raison de plus pour laquelle vous devriez no étendre les objets natifs :

Nous utilisons Magento qui utilise prototype.js et étend beaucoup de choses sur les objets natifs. Cela fonctionne bien jusqu'à ce que vous décidiez d'intégrer de nouvelles fonctionnalités et c'est là que les gros problèmes commencent.

Nous avons introduit des Webcomponents sur l'une de nos pages, et le webcomponents-lite.js décide de remplacer l'ensemble de l'Event Object (natif) dans IE (pourquoi ?). Cela casse bien sûr prototype.js qui à son tour casse Magento. (jusqu'à ce que vous trouviez le problème, vous risquez de passer beaucoup d'heures à le retracer).

Si vous aimez les ennuis, continuez à les faire !

5voto

Mark Amery Points 4705

Je vois trois raisons de ne pas le faire (à partir d'un site Web de l'UE). application au moins), dont deux seulement sont abordés dans les réponses existantes ici :

  1. Si vous vous y prenez mal, vous allez accidentellement ajouter une propriété énumérable à tous les objets du type étendu. Facile à contourner en utilisant Object.defineProperty qui crée des propriétés non énumérables par défaut.
  2. Vous pourriez provoquer un conflit avec une bibliothèque que vous utilisez. Cela peut être évité en faisant preuve de diligence ; vérifiez simplement quelles méthodes les bibliothèques que vous utilisez définissent avant d'ajouter quelque chose à un prototype, consultez les notes de version lors d'une mise à niveau et testez votre application.
  3. Vous pourriez provoquer un conflit avec une future version de l'environnement JavaScript natif.

Le point 3 est sans doute le plus important. Vous pouvez vous assurer, par le biais de tests, que les extensions de votre prototype ne provoquent aucun conflit avec les bibliothèques que vous utilisez, car usted décidez des bibliothèques que vous utilisez. Il n'en va pas de même pour les objets natifs, en supposant que votre code s'exécute dans un navigateur. Si vous définissez Array.prototype.swizzle(foo, bar) aujourd'hui, et demain Google ajoute Array.prototype.swizzle(bar, foo) à Chrome, vous risquez de vous retrouver avec des collègues déboussolés qui se demandent pourquoi .swizzle Le comportement de l'entreprise ne semble pas correspondre à ce qui est documenté sur MDN.

(Voir également le comment les manipulations de mootools avec des prototypes qu'ils ne possédaient pas ont obligé une méthode ES6 à être renommée pour éviter de casser le web. .)

On peut éviter cela en utilisant un préfixe spécifique à l'application pour les méthodes ajoutées aux objets natifs (par exemple, définir Array.prototype.myappSwizzle au lieu de Array.prototype.swizzle ), mais c'est plutôt moche ; on peut tout aussi bien résoudre le problème en utilisant des fonctions utilitaires autonomes au lieu d'augmenter les prototypes.

3voto

gman Points 9074

Perf est aussi une raison. Parfois, vous pouvez avoir besoin de boucler sur des clés. Il y a plusieurs façons de le faire

for (let key in object) { ... }
for (let key in object) { if (object.hasOwnProperty(key) { ... } }
for (let key of Object.keys(object)) { ... }

J'utilise habituellement for of Object.keys() comme il fait ce qu'il faut et est relativement laconique, il n'est pas nécessaire d'ajouter le contrôle.

Mais, c'est beaucoup plus lent .

for-of vs for-in perf results

Je devine juste la raison Object.keys est lent est évident, Object.keys() doit faire une allocation. En fait AFAIK il doit allouer une copie de toutes les clés depuis.

  const before = Object.keys(object);
  object.newProp = true;
  const after = Object.keys(object);

  before.join('') !== after.join('')

Il est possible que le moteur JS utilise une sorte de structure de clé immuable afin que Object.keys(object) renvoie une référence quelque chose qui itère sur des clés immuables et qui object.newProp crée un objet clé immuable entièrement nouveau mais peu importe, c'est clairement plus lent de 15x

Même en vérifiant hasOwnProperty est jusqu'à deux fois plus lent.

Le point de tout cela est que si vous avez un code sensible à la perforation et que vous avez besoin de boucler sur les touches, vous voulez être en mesure d'utiliser for in sans avoir à appeler hasOwnProperty . Vous ne pouvez le faire que si vous n'avez pas modifié les éléments suivants Object.prototype

Notez que si vous utilisez Object.defineProperty pour modifier le prototype si les éléments que vous ajoutez ne sont pas énumérables, ils n'affecteront pas le comportement de JavaScript dans les cas ci-dessus. Malheureusement, au moins dans Chrome 83, ils affectent les performances.

enter image description here

J'ai ajouté 3000 propriétés non dénombrables pour essayer de forcer l'apparition de problèmes de performance. Avec seulement 30 propriétés, les tests étaient trop serrés pour pouvoir dire s'il y avait un impact sur les performances.

https://jsperf.com/does-adding-non-enumerable-properties-affect-perf

Firefox 77 et Safari 13.1 ne montrent aucune différence de performance entre les classes augmentées et non augmentées. Peut-être que la v8 sera corrigée dans ce domaine et que vous pourrez ignorer les problèmes de performance.

Mais, laissez-moi aussi ajouter qu'il y a l'histoire de Array.prototype.smoosh . La version courte est que Mootools, une bibliothèque populaire, a fait son propre Array.prototype.flatten . Quand le comité de normalisation a essayé d'ajouter un natif Array.prototype.flatten ils ont trouvé qu'on ne pouvait pas sans casser beaucoup de sites. Les développeurs qui ont découvert la rupture ont suggéré de nommer la méthode es5. smoosh comme une blague mais les gens ont paniqué sans comprendre que c'était une blague. Ils se sont mis d'accord sur flat au lieu de flatten

La morale de l'histoire est que vous ne devriez pas étendre les objets natifs. Si vous le faites, vous risquez de rencontrer le même problème de casse de votre matériel et, à moins que votre bibliothèque particulière ne soit aussi populaire que MooTools, il est peu probable que les fournisseurs de navigateurs travaillent sur le problème que vous avez causé. Si votre bibliothèque devient aussi populaire, ce serait un peu méchant de forcer tout le monde à contourner le problème que vous avez causé. Donc, s'il vous plaît Ne pas étendre les objets natifs

0voto

fider Points 226

Édité :

Après un certain temps, j'ai changé d'avis - le prototype de la pollution est mauvais (j'ai toutefois laissé un exemple à la fin du billet).

Cela peut causer beaucoup plus de problèmes que ceux mentionnés dans les messages ci-dessus et ci-dessous.

Il est important d'avoir une norme unique dans tout l'univers JS/TS (ce serait bien d'avoir un npmjs cohérent).

Auparavant, j'ai écrit des conneries et encouragé les gens à le faire dans leurs bibliothèques - désolé pour cela :

Jeff Clayton semble être également bonne - le préfixe du nom de la méthode peut être votre nom du paquet suivi d'un trait de soulignement, par ex : Array.prototype.<custom>_Flatten (le préfixe de paquet non existant peut devenir un paquet existant dans le futur)


Partie de la réponse originale :

J'ai personnellement étendu les méthodes natives, j'utilise simplement x dans mes bibliothèques (je l'utilise également pour étendre les bibliothèques de tiers).

TS uniquement :

declare global {
  interface Array<T> {
    xFlatten(): ExtractArrayItemsRecursive<T>;
  }
}

JS+TS :

Array.prototype.xFlatten = function() { /*...*/ }

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