Pourquoi la mutation du ] d'un objet est-elle mauvaise pour la performance?

Du MDN docs pour la fonction setPrototypeOf standard ainsi que la propriété __proto__ non standard:

La mutation du [[Prototype]] d'un objet, peu importe la façon dont il est accompli, est fortement déconseillée, car elle est très lente et ralentit inévitablement l'exécution ultérieure dans les implémentations JavaScript modernes.

L'utilisation de Function.prototype pour ajouter des propriétés est la façon d'ajouter des fonctions membres aux classes javascript. Ensuite, comme suit:

 function Foo(){} function bar(){} var foo = new Foo(); // This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar; // Both cause this to be true: console.log(foo.__proto__.bar == bar); // true 

Pourquoi foo.__proto__.bar = bar; mal? Si c'est mauvais, ce n'est pas Foo.prototype.bar = bar; Tout aussi mauvais?

Alors pourquoi cet avertissement: il est très lent et ralentit inévitablement l'exécution ultérieure dans les implémentations JavaScript modernes . Sûrement Foo.prototype.bar = bar; N'est-ce pas si grave.

Mise à jour peut-être par mutation, ils ont signifié la réaffectation. Voir la réponse acceptée.

 // This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar; 

Non. Les deux font la même chose (comme foo.__proto__ === Foo.prototype ), et les deux sont bien. Ils créent simplement une propriété de bar sur l'objet Object.getPrototypeOf(foo) .

Ce que l'énoncé se réfère est attribuer à la propriété __proto__ elle-même:

 function Employee() {} var fred = new Employee(); // Assign a new object to __proto__ fred.__proto__ = Object.prototype; // Or equally: Object.setPrototypeOf(fred, Object.prototype); 

L'avertissement sur la page Object.prototype est plus détaillé:

La mutation du [[Prototype]] d'un objet est, par la nature de la façon dont les moteurs JavaScript modernes optimisent les accès aux propriétés , une opération très lente

Ils indiquent simplement que le changement de la chaîne prototype d'un objet déjà existant supprime les optimisations . Au lieu de cela, vous êtes censé créer un nouvel objet avec une chaîne de prototype différente via Object.create() .

Je ne pouvais pas trouver une référence explicite, mais si nous considérons comment les classes cachées de V8 sont implémentées, nous pouvons voir ce qui pourrait se dérouler ici. Lors du changement de la chaîne prototype d'un objet, son type interne change – il ne devient pas simplement une sous-classe comme lors de l'ajout d'une propriété mais est entièrement échangé. Cela signifie que toutes les optimisations de recherche de propriétés sont rincées, et le code précompilé devra être supprimé. Ou il retombe simplement sur un code non optimisé.

Quelques citations remarquables:

  • Brendan Eich (vous le connaissez) a déclaré

    L'écriture __proto__ est une douleur gigantesque à implémenter (doit être sérialisée pour faire un cycle de vérification) et crée toutes sortes de risques de confusion de type.

  • Brian Hackett (Mozilla) a déclaré :

    Permettre aux scripts de muter le prototype de pratiquement n'importe quel objet rend plus difficile de raisonner sur le comportement d'un script et rend VM, JIT et l'implémentation d'analyse plus complexes et plus bogues. L'inférence de type a eu plusieurs bogues en raison de __proto__ mutable et ne peut pas maintenir plusieurs invariants souhaitables en raison de cette fonctionnalité (c.-à-d. 'Les ensembles de type contiennent tous les objets de type possibles qui peuvent être réalisés pour une variable / propriété' et 'JSFunctions ont des types qui sont également des fonctions' ).

  • Jeff Walden a déclaré :

    Prototype de la mutation après la création, avec sa déstabilisation de la performance erratique, et l'impact sur les proxies et [[SetInheritance]]

  • Erik Corry (Google) a déclaré :

    Je ne m'attends pas à ce que de grands gains de performance ne produisent pas de proto non écrasable. Dans un code non optimisé, vous devez vérifier la chaîne du prototype dans le cas où les objets prototypes (et non leur identité) ont été modifiés. Dans le cas d'un code optimisé, vous pouvez revenir au code non optimisé si quelqu'un écrit sur proto. Donc, cela ne ferait pas autant de différence, du moins dans V8-Vilebrequin.

  • Eric Faust (Mozilla) a déclaré

    Lorsque vous définissez __proto__, non seulement vous arrêtez les chances que vous avez eu pour les optimisations futures d'Ion sur cet objet, mais vous forcez aussi à faire tourner le moteur vers toutes les autres inférences de type de type (informations sur les valeurs de retour de fonction, Ou des valeurs de propriété, peut-être) qui pensent qu'ils connaissent cet objet et leur disent de ne pas faire de nombreuses hypothèses non plus, ce qui implique une désoptimisation supplémentaire et peut-être une invalidation du jitcode existant.
    Changer le prototype d'un objet au milieu de l'exécution est vraiment un mousqueton méchant, et la seule façon de ne pas nous tromper est de le protéger en toute sécurité, mais la sécurité est lente.

__proto__ / setPrototypeOf ne sont pas identiques à ceux assignés au prototype d'objet. Par exemple, lorsque vous avez une fonction / objet avec des membres qui lui sont affectés:

 function Constructor(){ if (!(this instanceof Constructor)){ return new Constructor(); } } Constructor.data = 1; Constructor.staticMember = function(){ return this.data; } Constructor.prototype.instanceMember = function(){ return this.constructor.data; } Constructor.prototype.constructor = Constructor; // By doing the following, you are almost doing the same as assigning to // __proto__, but actually not the same :P var newObj = Object.create(Constructor);// BUT newObj is now an object and not a // function like !!!Constructor!!! // (typeof newObj === 'object' !== typeof Constructor === 'function'), and you // lost the ability to instantiate it, "new newObj" returns not a constructor, // you have .prototype but can't use it. newObj = Object.create(Constructor.prototype); // now you have access to newObj.instanceMember // but staticMember is not available. newObj instanceof Constructor is true // we can use a function like the original constructor to retain // functionality, like self invoking it newObj(), accessing static // members, etc, which isn't possible with Object.create var newObj = function(){ if (!(this instanceof newObj)){ return new newObj(); } }; newObj.__proto__ = Constructor; newObj.prototype.__proto__ = Constructor.prototype; newObj.data = 2; (new newObj()).instanceMember(); //2 newObj().instanceMember(); // 2 newObj.staticMember(); // 2 newObj() instanceof Constructor; // is true Constructor.staticMember(); // 1 

Tout le monde semble se concentrer uniquement sur le prototype, et oublie que les fonctions peuvent avoir des membres qui lui sont assignés et instanciés après la mutation. Il n'y a actuellement aucune autre façon de le faire sans utiliser __proto__ / setPrototypeOf . A peine n'importe qui utilise un constructeur sans pouvoir hériter d'une fonction constructeur parent, et Object.create ne parvient pas à servir.

Et plus, il s'agit de deux appels Object.create , qui, à l'heure actuelle, sont impitoyablement lents dans V8 (navigateur et Node), ce qui rend __proto__ un choix plus viable

Oui .prototype = est tout aussi mauvais, d'où le libellé "peu importe comment il est accompli". Le prototype est un pseudo objet pour étendre la fonctionnalité au niveau de la classe. Sa nature dynamique ralentit l'exécution du script. L'ajout d'une fonction au niveau de l'instance, d'autre part, entraîne beaucoup moins de frais généraux.