web-dev-qa-db-fra.com

Comment définir le prototype d'un objet JavaScript déjà instancié?

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

Ma motivation est de définir des prototypes appropriés pour des objets sérialisés à partir de littéraux .NET en JavaScript.

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'appel de .NET JavaScriptSerializer sur un objet Dictionary<string,string>.

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

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

Comme vous pouvez le constater, foo devient un tableau d'objets. J'aimerais pouvoir initialiser foo avec un prototype approprié. Je veux pas vouloir modifier le Object.prototype ni le Array.prototype. Comment puis-je faire ceci?

99

EDIT Février 2012: la réponse ci-dessous n'est plus exacte. __proto__ est ajouté à ECMAScript 6 en tant qu '"optionnel normatif", ce qui signifie qu'il n'est pas obligatoire de le mettre en œuvre, mais que si c'est le cas, il doit respecter l'ensemble de règles donné. Ce problème n’a pas encore été résolu, mais au moins cela fera officiellement partie des spécifications de JavaScript.

Cette question est beaucoup plus compliquée qu'il n'y parait à la surface et dépasse le niveau de rémunération 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. Le changer ne reflète pas l'objet lui-même, il est reflété lorsque cet objet est utilisé en tant que constructeur pour d'autres objets et ne sert à rien de 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é interne [[prototype]] qui pointe vers le prototype actuel. La façon dont cela fonctionne est que chaque fois qu'une propriété sur un objet est appelée, elle commence par l'objet, puis monte dans la chaîne [[prototype]] jusqu'à ce qu'elle trouve une correspondance, ou échoue, après le prototype de l'objet racine. C'est ainsi que Javascript permet la construction et la modification d'objets à l'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 Webkit que je connaisse, d'autres. Cette propriété pointe sur la propriété interne [[prototype]] et permet la modification post-création sur les objets. Toutes les propriétés et fonctions changeront instantanément pour correspondre au prototype en raison de cette recherche chaînée.

Bien que normalisée à l’heure actuelle, cette fonctionnalité n’est toujours pas une partie obligatoire de JavaScript et, dans les langages qui la prennent en charge, il est très probable que votre code tombe dans la catégorie "non optimisé". 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 d'extraordinaire comme de modifier __proto__, ils n'optimiseront pas votre code.

Cet article https://bugzilla.mozilla.org/show_bug.cgi?id=607863 traite spécifiquement des implémentations actuelles de __proto__ et de leurs différences. Chaque mise en œuvre le fait différemment, car c'est un problème difficile et non résolu. Tout en Javascript est modifiable, sauf a.) La syntaxe b.) Les objets hôtes (le DOM existe techniquement en dehors de Javascript) et c.) __proto__. Le reste est entièrement entre vos mains et celles de tous les autres développeurs, vous pouvez donc voir pourquoi __proto__ est visible comme un pouce endolori. 

Il y a une chose que __proto__ permet de faire qui est autrement impossible à faire: la désignation d'un prototype d'objets lors de l'exécution distinct de son constructeur. Ceci est un cas d'utilisation important et l'une des principales raisons pour lesquelles __proto__ n'est pas déjà mort. Il est assez important que cela ait été un sujet de discussion sérieux dans la formulation d'Harmony, ou bientôt connu sous le nom d'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 le cas. la cloche indiquant les jours de __proto__ est officiellement numérotée.

À court terme, vous pouvez utiliser __proto__ si vous ciblez les navigateurs qui le prennent en charge (pas IE, et aucun IE ne le fera jamais). Il est probable que cela fonctionnera dans webkit et moz pour les 10 prochaines années, car ES6 ne sera pas finalisé avant 2013.

Brendan Eich - re: Approche des nouvelles méthodes objet dans ES5 :

Désolé, ... mais __proto__ paramétrable, mis à part le cas d'utilisation d'initialiseur d'objet (c'est-à-dire, sur un nouvel objet non encore joignable, analogue à Object.create de ES5), est une idée terrible. J'écris ceci après avoir conçu et mis en œuvre le __proto__ réglable il y a plus de 12 ans.

... le manque de stratification est un problème (considérons 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 le lissage. [des contrôles constants de récursion infinie sont requis]

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

110
user748221

ES6 spécifie enfin Object.setPrototypeOf (objet, prototype) qui est déjà implémenté dans Chrome et Firefox.

28
Joel Richard

Vous pouvez utiliser constructor sur une instance d'un objet pour modifier le prototype d'un objet en 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 propriété bar à toutes les instances de Foo en procédant comme suit:

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

Voici un violon montrant la preuve de concept: http://jsfiddle.net/C2cpw/ . Pas tout à fait sûr de savoir comment les anciens navigateurs utiliseront cette approche, mais je suis sûr que cela devrait plutôt bien faire le travail.

Si votre intention est de mélanger des fonctionnalités dans des objets, cet extrait de code devrait faire l'affaire:

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;
    }
  };
};
14
Tim

Vous pouvez faire foo.__proto__ = FooClass.prototype, autant que je sache, supporté par Firefox, Chrome et Safari. N'oubliez pas que la propriété __proto__ est non standard et peut disparaître à un moment donné.

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

9
Wladimir Palant

Vous ne pouvez pas modifier le prototype d'un objet JavaScript déjà instancié dans un navigateur croisé. Comme d'autres l'ont mentionné, vos options incluent:

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

Ni l'un ni l'autre ne sont particulièrement intéressants, en particulier si vous devez effectuer une boucle récursive sur un objet en objets internes pour modifier efficacement le prototype d'un élément.

Solution alternative à la question

Je vais jeter un regard plus abstrait sur les fonctionnalités que vous souhaitez apparemment.

En principe, les prototypes/méthodes permettent simplement de grouper des fonctions basées sur un objet.
Au lieu d'écrire 

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

vous écrivez

'   test  '.trim();

La syntaxe ci-dessus a été baptisée le terme OOP en raison de la syntaxe object.method () . Certains des principaux avantages des OOP par rapport à la programmation fonctionnelle traditionnelle incluent: 

  1. Méthodes courtes noms et moins de variables obj.replace('needle','replaced') vs devoir mémoriser des noms comme str_replace ( 'foo' , 'bar' , 'subject') et l'emplacement des différentes variables
  2. la méthode chaining (string.trim().split().join()) est potentiellement plus facile à modifier et à écrire que les fonctions imbriquées join(split(trim(string)) 

Malheureusement, en JavaScript (comme indiqué ci-dessus), vous ne pouvez pas modifier un prototype existant. Idéalement, vous pouvez modifier Object.prototype uniquement pour les objets ci-dessus, mais malheureusement, modifier Object.prototype risquerait de rompre les scripts (ce qui entraînerait une collision et une substitution de propriété).

Il n'y a pas de juste milieu couramment utilisé entre ces 2 styles de programmation et pas de moyen OOP pour organiser les fonctions personnalisées.

UnlimitJS fournit un moyen terme 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 le script multi-navigateurs de 450 octets (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) qui constitue l’essentiel des problèmes de collision de propriétés de JavaScript avec JavaScript

En utilisant votre code ci-dessus, je créerais simplement un espace de noms de fonctions que vous avez l'intention d'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, la fonction peut être appelée en tant que méthode sur un objet. C'est comme un juste milieu entre la OOP et les routes fonctionnelles.

4
William A

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 dans celle-ci.

// 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.

Dans le constructeur Custom, il vous suffit de copier toutes les propriétés de l'objet d'origine vers le nouvel objet d'instance. 

Ce nouvel objet d'instance contient toutes les propriétés de l'objet d'origine (elles y 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.

3
Šime Vidas

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

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

Vous pouvez peut-être obtenir ce que vous voulez d'une autre manière, si vous êtes prêt à aborder le problème sous un angle différent: que devez-vous faire pour jouer avec le prototype?

2
Nick Husher

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 avez seulement besoin d'un constructeur qui prend un tableau en argument.

1
rewritten

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);
0
Yene Mulatu
foo.prototype.myFunction = function(){alert("me");}
0
PhD

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

Ce que vous pouvez faire est la suivante ( AVERTISSEMENT: FESTERING CODE AHEAD ):

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 à quiconque croise son chemin (cela ressemble à un tableau, mais tout ira mal si vous essayez de le manipuler).

Pourquoi ne pas suivre la bonne voie et ajouter des méthodes/propriétés directement à foo, ou utiliser un constructeur et enregistrer votre tableau en tant que 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"}]
0
Ricardo Tomasi