Comment définir le prototype d'un objet JavaScript qui a déjà été créé?

Supposons que j'ai un objet foo dans mon code JavaScript. foo est un objet complexe et il est généré ailleurs. Comment puis-je changer le prototype de l'objet foo ?

Mise à jour : Je me rends compte que je dois être plus précis sur ce que je fais ici.

Supposons que j'ai écrit le code JavaScript suivant dans une page ASP.net.

 var foo = <%=MyData %>; 

Supposons que MyData soit le résultat de l'invocation du .net JavaScriptSerializer sur un objet Dictionary<string,string> dans mon code .net.

Au moment de l'exécution, ceci devient le suivant:

 var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}]; 

Comme vous pouvez le voir, foo devient un ensemble d'objets. Je voudrais pouvoir initialiser foo avec un prototype approprié. Je ne veux pas modifier les prototypes Object ou Array. Comment puis-je faire ceci?

EDIT Feb. 2012: la réponse ci-dessous n'est plus précise. __proto__ est ajouté à ECMAScript 6 comme "optionnel normatif", ce qui signifie qu'il n'est pas nécessaire d'être implémenté, mais s'il est, il doit suivre l'ensemble des règles. Ceci n'est actuellement pas résolu mais au moins il fera officiellement partie des spécifications de JavaScript.

Cette question est beaucoup plus compliquée qu'elle ne semble en surface, et au-delà de la note payante de la plupart des gens en ce qui concerne la connaissance des composants internes de Javascript.

La propriété prototype d'un objet est utilisée lors de la création de nouveaux objets enfants de cet objet. La modification ne reflète pas dans l'objet lui-même, mais elle se reflète lorsque l'objection est utilisée comme constructeur pour d'autres objets et n'a pas d'utilité pour changer le prototype d'un objet existant.

 function myFactory(){}; myFactory.prototype = someOtherObject; var newChild = new myFactory; newChild.__proto__ === myFactory.prototype === someOtherObject; //true 

Les objets ont une propriété [[prototype] interne qui indique le prototype actuel. La façon dont il fonctionne est chaque fois qu'une propriété sur un objet est appelé, elle commencera à l'objet, puis passe à travers la chaîne [[prototype]] jusqu'à ce qu'il trouve une correspondance ou échoue après le prototype d'objet racine. C'est ainsi que Javascript permet de construire et de modifier des objets d'exécution; Il a un plan pour chercher ce dont il a besoin.

La propriété __proto__ existe dans certaines implémentations (beaucoup maintenant): toute implémentation de Mozilla, toutes les applications Web dont je connais, d'autres. Cette propriété pointe vers la propriété [[prototype] interne et permet la modification après la création sur les objets. Toutes les propriétés et les fonctions changent instantanément pour correspondre au prototype en raison de cette recherche enchaînée.

Cette fonctionnalité, tout en étant standardisée maintenant, n'est toujours pas une partie requise de JavaScript, et dans les langues qui la supportent, il est fort probable que vous abattrez votre code dans la catégorie "non optimisée". Les moteurs JS doivent faire de leur mieux pour classer le code, en particulier le code «chaud» auquel on accède très souvent, et si vous faites quelque chose de raffiné comme modifiant __proto__ ils ne vont pas optimiser votre code.

Les publications https://bugzilla.mozilla.org/show_bug.cgi?id=607863 __proto__ spécifiquement des implémentations actuelles de __proto__ et des différences entre elles. Chaque implémentation est différente, car c'est un problème difficile et non résolu. Tout dans Javascript est mutable sauf a.) La syntaxe b.) Objets hôtes (le DOM existe en dehors de Javascript techniquement) et c.) __proto__ . Le reste est complètement entre vous et tous les autres développeurs, afin que vous puissiez voir pourquoi __proto__ détache comme un pouce endolori.

Il y a une chose que __proto__ permet d'être impossible: la désignation d'un prototype d'objet au moment de l'exécution se distingue de son constructeur. C'est un cas d'utilisation important et est l'une des principales raisons pour lesquelles __proto__ n'est pas déjà mort. Il est important que ce soit un point de discussion sérieux dans la formulation d'Harmony ou qu'il soit appelé prochainement ECMAScript 6. La possibilité de spécifier le prototype d'un objet lors de la création fera partie de la prochaine version de Javascript et ce sera La cloche indiquant __proto__ jours sont formellement numérotés.

À court terme, vous pouvez utiliser __proto__ si vous ciblez les navigateurs qui le supportent (pas IE et aucun IE ne l'aura). Il est probable que cela fonctionnera en webkit et moz pour les 10 prochaines années, car ES6 ne sera finalisé qu'en 2013.

Brendan Eich – re: Approche des nouvelles méthodes Object dans ES5 :

Désolé, … mais __proto__ , en dehors du cas d'utilisation de l' __proto__ de l'objet (c'est-à-dire, sur un nouvel objet non accessible, analogue à Objet.create d'ES5), est une idée terrible. J'écris ceci en ayant conçu et mis en œuvre __proto__ réglable __proto__ plus de 12 ans.

… le manque de stratification est un problème (considérez les données JSON avec une clé "__proto__" ). Et pire encore, la mutabilité signifie que les implémentations doivent vérifier les chaînes de prototypes cycliques afin d'éviter d'y évoquer. [Des contrôles constants pour une récurrence infinie sont requis]

Enfin, la mutation __proto__ sur un objet existant peut casser des méthodes non génériques dans le nouvel objet prototype, qui ne peut pas fonctionner sur l'objet récepteur (direct) dont __proto__ est en cours de définition. C'est simplement une mauvaise pratique, une forme de confusion de type intentionnel, en général.

ES6 définit finalement Object.setPrototypeOf (objet, prototype) déjà implémenté dans Chrome et Firefox.

Vous pouvez utiliser le constructor sur une instance d'un objet pour modifier le prototype d'un objet sur place. Je crois que c'est ce que vous demandez de faire.

Cela signifie que si vous avez foo qui est une instance de Foo :

 function Foo() {} var foo = new Foo(); 

Vous pouvez ajouter une bar propriétés à toutes les instances de Foo en procédant comme suit:

 foo.constructor.prototype.bar = "bar"; 

Voici une violon montrant la preuve de concept: http://jsfiddle.net/C2cpw/ . Pas terriblement sûr de la façon dont les navigateurs plus anciens vont utiliser cette approche, mais je suis très sûr que cela devrait faire le travail très bien.

Si votre intention est de combiner la fonctionnalité dans les objets, cet extrait devrait faire le travail:

 function mix() { var mixins = arguments, i = 0, len = mixins.length; return { into: function (target) { var mixin, key; if (target == null) { throw new TypeError("Cannot mix into null or undefined values."); } for (; i < len; i += 1) { mixin = mixins[i]; for (key in mixin) { target[key] = mixin[key]; } // Take care of IE clobbering `toString` and `valueOf` if (mixin && mixin.toString !== Object.prototype.toString) { target.toString = mixin.toString; } else if (mixin && mixin.valueOf !== Object.prototype.valueOf) { target.valueOf = mixin.valueOf; } } return target; } }; }; 

Vous pouvez faire foo.__proto__ = FooClass.prototype , AFAIK pris en charge par Firefox, Chrome et Safari. Gardez à l'esprit que la propriété __proto__ n'est pas standard et pourrait disparaître à un moment donné.

Documentation: https://developer.mozilla.org/fr/JavaScript/Reference/Global_Objects/Object/proto . Consultez http://www.mail-archive.com/[email protected]/msg00392.html pour obtenir une explication pour laquelle il n'y a pas de Object.setPrototypeOf() et pourquoi __proto__ est obsolète.

Vous ne pouvez pas modifier le prototype d'un objet JavaScript qui a déjà été implanté de manière croisée. Comme d'autres l'ont mentionné, vos options comprennent:

  1. __proto__ propriété __proto__ non standard / cross browser
  2. Copier les propriétés des objets vers un nouvel objet

Ce n'est pas non plus particulièrement génial, surtout si vous devez passer de manière récursive à travers un objet dans des objets internes pour changer efficacement un prototype entier d'éléments.

Solution alternative à la question

Je vais aborder de manière plus abstraite les fonctionnalités que vous désirez.

Fondamentalement, les prototypes / méthodes permettent simplement de grouper des fonctions en fonction d'un objet.
Au lieu d'écrire

 function trim(x){ /* implementation */ } trim(' test '); 

vous écrivez

 ' test '.trim(); 

La syntaxe ci-dessus a été inventée par le terme OOP en raison de la syntaxe object.method (). L'avantage majeur de l'OOP sur la programmation fonctionnelle traditionnelle comprend:

  1. Les noms de méthodes courtes et moins de variables obj.replace('needle','replaced') vs avoir à se rappeler de noms comme str_replace ( 'foo' , 'bar' , 'subject') et l'emplacement des différentes variables
  2. Méthode de chaînage ( string.trim().split().join() ) est potentiellement plus facile à modifier et à écrire, puis aux fonctions imbriquées join(split(trim(string))

Malheureusement en JavaScript (comme indiqué ci-dessus), vous ne pouvez pas modifier un prototype déjà existant. Idéalement, vous pouvez modifier Object.prototype uniquement pour les objets ci-dessus, mais, malheureusement, modifier Object.prototype risque de briser des scripts (résultant en une collision de propriété et une substitution).

Il n'y a pas de moyen terme couramment utilisé entre ces 2 styles de programmation et aucun mode OOP pour organiser des fonctions personnalisées.

UnlimitJS fournit un terrain d'entente qui vous permet de définir des méthodes personnalisées. Cela évite:

  1. Collision de propriété, car elle ne prolonge pas les prototypes d'objets
  2. Permet toujours une syntaxe de chaînage OOP
  3. C'est un navigateur croisé de 450 octets (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) qui explique que les problèmes de collision de propriétés de prototype de JavaScript de Unlimit

En utilisant votre code ci-dessus, je créerais simplement un espace de noms de fonctions que vous souhaitez appeler contre l'objet.

Voici un exemple:

 var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}]; // define namespace with methods var $ = { log:function(){ console.log(this); return this; }[Unlimit](), alert:function(){ alert(''+this); }[Unlimit]() } foo[$.log]() [$.log]() [$.alert](); 

Vous pouvez lire plus d'exemples ici UnlimitJS . Fondamentalement, lorsque vous appelez [Unlimit]() sur une fonction, il permet à la fonction d'appeler comme méthode sur un objet. C'est comme un milieu de terrain entre l'OOP et les routes fonctionnelles.

Vous pouvez définir votre fonction de constructeur de proxy, puis créer une nouvelle instance et copier toutes les propriétés de l'objet d'origine.

 // your original object var obj = { 'foo': true }; // your constructor - "the new prototype" function Custom(obj) { for ( prop in obj ) { if ( obj.hasOwnProperty(prop) ) { this[prop] = obj[prop]; } } } // the properties of the new prototype Custom.prototype.bar = true; // pass your original object into the constructor var obj2 = new Custom(obj); // the constructor instance contains all properties from the original // object and also all properties inherited by the new prototype obj2.foo; // true obj2.bar; // true 

Démo en direct: http://jsfiddle.net/6Xq3P/

Le constructeur Custom représente le nouveau prototype, ergo, son objet Custom.prototype contient toutes les nouvelles propriétés que vous souhaitez utiliser avec votre objet d'origine.

À l'intérieur du constructeur Custom , il suffit de copier toutes les propriétés de l'objet d'origine vers le nouvel objet d'instance.

Cet objet de nouvelle instance contient toutes les propriétés de l'objet d'origine (elles ont été copiées dans le constructeur), ainsi que toutes les nouvelles propriétés définies dans Custom.prototype (car le nouvel objet est une instance Custom ).

Vous ne pouvez pas modifier la référence [[prototype]] d'objets déjà construits, autant que je sache. Vous pouvez modifier la propriété prototype de la fonction constructeur d'origine, mais, comme vous l'avez déjà expliqué, ce constructeur est Object , et la modification des constructions JS de base est une mauvaise chose.

Vous pouvez créer un objet proxy de l'objet construit qui implémente les fonctionnalités supplémentaires dont vous avez besoin. Vous pouvez également monkeypatch les méthodes et les comportements supplémentaires en assignant directement à l'objet en question.

Peut-être que vous pouvez obtenir ce que vous voulez d'une autre façon, si vous êtes prêt à vous approcher sous un angle différent: qu'est-ce que vous devez faire, cela implique de déranger le prototype?

Si vous connaissez le prototype, pourquoi ne pas l'injecter dans le code?

 var foo = new MyPrototype(<%= MyData %>); 

Donc, une fois que les données sont sérialisées, vous obtenez

 var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]); 

Maintenant, vous n'avez besoin que d'un constructeur qui prend un tableau comme argument.

 foo.prototype.myFunction = function(){alert("me");} 

Il n'y a aucun moyen d'hériter de Array ou de "sous-classe".

Ce que vous pouvez faire, c'est ceci ( AVERTISSEMENT: CODE DE FESTERING AVANT ):

 function Foo(arr){ [].push.apply(this, arr) } Foo.prototype = [] Foo.prototype.something = 123 var foo = new Foo(<%=MyData %>) foo.length // => 2 foo[0] // => {"A":"1","B":"2"} foo.something // => 123 

Cela fonctionne, mais causera certains problèmes pour quiconque croise son chemin (il ressemble à un tableau, mais les choses vont mal si vous essayez de le manipuler).

Pourquoi ne passez-vous pas la voie sine et ajoutez des méthodes / propriétés directement à foo , ou utilisez-vous un constructeur et enregistrez-vous votre tableau comme propriété?

 function Foo(arr){ this.items = arr } Foo.prototype = { someMethod : function(){ ... } //... } var foo = new Foo(<%=MyData %>) foo.items // => [{"A":"1","B":"2"},{"X":"7","Y":"8"}] 

Si vous voulez créer un prototype à la volée, c'est l'un des moyens

 function OntheFlyProto (info){ this.items = info; this.y =-1; for(var i = 0; i < this.items.length ; i++){ OntheFlyProto.prototype["get"+this.items[i].name] = function (){ this.y++; return this.items[this.y].value; } } } var foo = [{name:"one", value:1},{name:"two", value:2}]; v = new OntheFlyProto(foo);