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


106

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

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

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

var foo = <%=MyData %>;

Supposons que ce MyDatasoit le résultat de l'appel du .NET JavaScriptSerializersur un Dictionary<string,string>objet.

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

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

Comme vous pouvez le voir, foodevient un tableau d'objets. Je souhaite pouvoir initialiser fooavec un prototype approprié. Je ne pas veux modifier le Object.prototypeni Array.prototype. Comment puis-je faire ceci?


Voulez-vous l'ajouter à son prototype existant ou le changer pour un nouveau prototype?
SLaks

Voulez-vous dire apporter des modifications au prototype - ou réellement changer le prototype comme en remplaçant un prototype et le remplacer par un autre. Je ne suis même pas sûr que le dernier cas soit possible.
James Gaunt

2
Voulez-vous dire la propriété prototype explicite ou le lien prototype implicite ? (Ces deux choses sont très différentes)
Šime Vidas

J'étais à l'origine concerné par ceci: stackoverflow.com/questions/7013545/…
Vivian River

1
Connaissez-vous Backbone extendou Google goog.inherit? De nombreux développeurs proposent des moyens de créer un héritage avant d'appeler le newconstructeur âgé, ce qui est avant que nous ne soyons donnés Object.createet que nous n'avions pas à nous soucier de la substitution Object.prototype.
Ryan

Réponses:


114

EDIT Février 2012: la réponse ci-dessous n'est plus exacte. __proto__ est ajouté à ECMAScript 6 en tant que "normatif facultatif", ce qui signifie qu'il n'est pas nécessaire de l'implémenter, mais si c'est le cas, il doit suivre l'ensemble de règles donné. Ceci n'est actuellement pas 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 paraît à première vue, et au-delà de la rémunération de la plupart des gens en ce qui concerne la connaissance des internes Javascript.

La prototypepropriété d'un objet est utilisée lors de la création de nouveaux objets enfants de cet objet. Le changer ne se reflète pas dans l'objet lui-même, mais est plutôt reflété lorsque cet objet est utilisé comme constructeur pour d'autres objets, et n'a aucune 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é 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 commencera à l'objet, puis remontera 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 rechercher ce dont il a besoin.

La __proto__propriété existe dans certaines implémentations (beaucoup maintenant): toute implémentation de Mozilla, toutes les webkit que je connais, quelques autres. Cette propriété pointe vers la propriété interne [[prototype]] et permet de modifier post-création sur les objets. Toutes les propriétés et fonctions basculeront instantanément pour correspondre au prototype en raison de cette recherche en chaîne.

Cette fonctionnalité, bien qu'étant normalisée actuellement, n'est toujours pas une partie obligatoire de JavaScript, et dans les langages qui la supportent, elle a une forte probabilité de faire tomber votre code 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 la modification __proto__, ils n'optimiseront pas du tout votre code.

Cet article https://bugzilla.mozilla.org/show_bug.cgi?id=607863 traite spécifiquement des implémentations actuelles __proto__et des différences entre elles. Chaque implémentation le fait différemment, car c'est un problème difficile et non résolu. Tout en Javascript est mutable, 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 les mains de vous et de tous les autres développeurs, vous pouvez donc voir pourquoi cela __proto__ressort comme un pouce endolori.

Il y a une chose qui __proto__permet que cela soit autrement impossible à faire: la désignation d'un prototype d'objets à l'exécution séparé de son constructeur. Il s'agit d'un cas d'utilisation important et l'une des principales raisons pour lesquelles il __proto__n'est pas déjà mort. C'est suffisamment important pour que cela ait été un point 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 la cloche indiquant __proto__les jours est formellement numérotée.

À court terme, vous pouvez l'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 pendant les 10 prochaines années, car ES6 ne sera pas finalisé avant 2013.

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

Désolé, ... mais paramétrable __proto__, mis à part le cas d'utilisation de l'initialiseur d'objet (c'est-à-dire sur un nouvel objet non encore accessible, analogue à Object.create d'ES5), est une idée terrible. J'écris ceci après avoir conçu et implémenté configurable il y a __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, la mutabilité signifie que les implémentations doivent vérifier les chaînes prototypes cycliques afin d'éviter les boucles. [des vérifications constantes de la récursivité infinie sont requises]

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


Excellente ventilation! Bien que ce ne soit pas tout à fait la même chose qu'un prototype mutable, ECMA Harmony implémentera probablement des proxies , qui vous permettent de caler des fonctionnalités supplémentaires sur des objets particuliers en utilisant un modèle fourre-tout.
Nick Husher

2
Le problème de Brendan Eich est originaire des langages de prototypage dans leur ensemble. Il est possible que __proto__ non-writable, configurableremédie à ces problèmes en forçant l'utilisateur à reconfigurer explicitement la propriété. En fin de compte, les mauvaises pratiques sont associées aux abus des capacités d'une langue, pas aux capacités elles-mêmes. Le __proto__ inscriptible n'est pas rare. Il existe de nombreuses autres propriétés inscriptibles non énumérables et, bien qu'il existe des dangers, il existe également des bonnes pratiques. La tête d'un marteau ne doit pas être enlevée simplement parce qu'elle peut être mal utilisée pour blesser quelqu'un.
Swivel le

La mutation __proto__est nécessaire, car Object.create ne produira que des objets, pas des fonctions par exemple, ni des symboles, des expressions régulières, des éléments DOM ou d'autres objets hôtes. Si vous voulez que vos objets soient appelables, ou spéciaux d'une autre manière, mais que vous changez toujours leur chaîne de prototypes, vous êtes bloqué sans paramétrable __proto__ni
proxy

2
Ce serait mieux si cela n'était pas fait avec une propriété magique, mais plutôt avec Object.setPrototype, éliminant le problème " __proto__in JSON". Les autres préoccupations de Brendan Eich sont risibles. Les chaînes de prototypes récursives ou l'utilisation d'un prototype avec des méthodes inadéquates pour l'objet donné sont des erreurs de programmeur, pas des fautes de langage et ne devraient pas être un facteur, et en plus elles peuvent se produire avec Object.create aussi bien qu'avec librement configurable __proto__.
user2451227

1
IE 10 prend en charge __proto__.
kzh


14

Vous pouvez utiliser constructorsur 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 fooune instance de Foo:

function Foo() {}

var foo = new Foo();

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

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

Voici un violon montrant la preuve de concept: http://jsfiddle.net/C2cpw/ . Je ne sais pas vraiment comment les navigateurs plus anciens s'en tireront avec cette approche, mais je suis presque 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 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;
    }
  };
};

1
+1: C'est informatif et intéressant, mais cela ne m'aide pas vraiment à obtenir ce que je veux. Je mets à jour ma question pour être plus précise.
Vivian River

Vous pouvez également utiliserfoo.__proto__.bar = 'bar';
Jam Risser

9

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

Documentation: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Consultez également http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html pour savoir pourquoi il n'y en a pas Object.setPrototypeOf()et pourquoi __proto__est obsolète.


3

Vous pouvez définir votre fonction de constructeur de proxy, puis créer une nouvelle instance et y 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 Customconstructeur représente le nouveau prototype, ergo, sonCustom.prototype objet contient toutes les nouvelles propriétés que vous souhaitez utiliser avec votre objet d'origine.

À l'intérieur du Customconstructeur, copiez simplement 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 à l'intérieur du constructeur), ainsi que toutes les nouvelles propriétés définies à l'intérieur Custom.prototype(car le nouvel objet est une Custominstance).


3

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

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

Ni l'un ni l'autre ne sont particulièrement intéressants, surtout si vous devez effectuer une boucle récursive à travers un objet dans des objets internes pour modifier efficacement le prototype entier d'un élément.

Solution alternative à la question

Je vais jeter un regard plus abstrait sur les fonctionnalités que vous désirez.

Fondamentalement, les prototypes / méthodes permettent simplement de regrouper les 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 le terme POO en raison de la syntaxe object.method (). Certains des principaux avantages de la POO par rapport à la programmation fonctionnelle traditionnelle comprennent:

  1. Noms de méthodes courts et moins de variables obj.replace('needle','replaced')vs devoir se souvenir des noms comme str_replace ( 'foo' , 'bar' , 'subject')et de l'emplacement des différentes variables
  2. method chaining ( string.trim().split().join()) est une fonction potentiellement plus facile à modifier et à écrire que des fonctions imbriquéesjoin(split(trim(string))

Malheureusement, en JavaScript (comme indiqué ci-dessus), vous ne pouvez pas modifier un prototype déjà existant. Idéalement ci-dessus, vous pouvez modifier Object.prototypeuniquement pour l'objet donné ci-dessus, mais malheureusement, la modification Object.prototyperisquerait de casser les scripts (entraînant une collision et un remplacement de propriété).

Il n'y a pas de terrain d'entente couramment utilisé entre ces 2 styles de programmation, ni de moyen OOP d'organiser les 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 n'étend pas les prototypes des objets
  2. Permet toujours une syntaxe de chaînage OOP
  3. C'est un script de navigateur croisé de 450 octets (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) qui illimite une grande partie des problèmes de collision de propriété prototype de 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]()une fonction, cela permet à la fonction d'être appelée en tant que méthode sur un objet. C'est comme un juste milieu entre la POO et les routes fonctionnelles.


2

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

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

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


Quelqu'un d'autre peut-il commenter l'exactitude de cela?
Vivian River

Il semble, sur la base de ce terrible jsfiddle, que vous pouvez modifier le prototype d'un objet existant en modifiant sa __proto__propriété. Cela ne fonctionnera que dans les navigateurs qui prennent en charge la __proto__notation, à savoir Chrome et Firefox, et il est obsolète . Donc, en bref, vous pouvez changer le [[prototype]]d'un objet, mais vous ne devriez probablement pas.
Nick Husher

1

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

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

Ainsi, une fois les données 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.


0

Il n'y a aucun moyen d'en hériter Arrayou de le «sous-classer».

Voici ce que vous pouvez faire ( 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 les choses iront mal si vous essayez de le manipuler).

Pourquoi ne pas aller dans la bonne direction et ajouter directement des méthodes / propriétés à 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

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);

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

Non, tu ne peux pas. C'est une propriété statique.
SLaks

@SLaks prototypeest statique? Je ne sais pas ce que tu veux dire.
Šime Vidas

1
prototypeest une propriété d'une fonction, pas d'un objet. Object.prototypeexiste; {}.prototypepas.
SLaks

Si vous ne vous souciez pas de la compatibilité du navigateur, vous pouvez utiliser Object.getPrototypeOf(foo)pour obtenir l'objet prototype du constructeur. Vous pouvez modifier les propriétés là-dessus pour modifier le prototype de l'objet. Cela ne fonctionne que dans les navigateurs récents, mais je ne pouvais pas vous dire lesquels.
Nick Husher

@TeslaNick: Vous pouvez simplement aller modifier Object.prototypedirectement parce que c'est probablement ce Object.getPrototypeOf(foo)qui reviendra. Pas très utile.
Wladimir Palant
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.