Come impostare il prototipo di un oggetto JavaScript che è già stato istanziato?


106

Supponiamo che io abbia un oggetto foonel mio codice JavaScript. fooè un oggetto complesso e viene generato da qualche altra parte. Come posso modificare il prototipo foodell'oggetto?

La mia motivazione è impostare prototipi appropriati per oggetti serializzati da .NET a JavaScript letterali.

Supponiamo che abbia scritto il seguente codice JavaScript all'interno di una pagina ASP.NET.

var foo = <%=MyData %>;

Supponiamo che MyDatasia il risultato dell'invocazione di .NET JavaScriptSerializersu un Dictionary<string,string>oggetto.

In fase di esecuzione, diventa il seguente:

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

Come puoi vedere, foodiventa un array di oggetti. Mi piacerebbe poter inizializzare foocon un prototipo appropriato. Io non voglio modificare il Object.prototypeArray.prototype. Come posso fare questo?


Vuoi aggiungere al suo prototipo esistente o passarlo a un nuovo prototipo?
SLaks

Intendi apportare modifiche al prototipo - o effettivamente modificare il prototipo come nel cambiare un prototipo e sostituirlo con un altro. Non sono nemmeno sicuro che il caso successivo sia possibile.
James Gaunt

2
Intendi la proprietà del prototipo esplicito o il collegamento del prototipo implicito ? (Quei due sono due cose molto diverse)
Šime Vidas

Inizialmente ero preoccupato per questo: stackoverflow.com/questions/7013545/…
Vivian River

1
Hai familiarità con Backbone extendo Google goog.inherit? Molti sviluppatori forniscono modi per costruire l'ereditarietà prima di chiamare il vecchio newcostruttore, il che è prima che ci fosse dato Object.createe non dovevamo preoccuparci di sovrascrivere Object.prototype.
Ryan

Risposte:


114

EDIT Febbraio 2012: la risposta qui sotto non è più accurata. __proto__ viene aggiunto a ECMAScript 6 come "normativo facoltativo", il che significa che non è necessario implementarlo, ma se lo è, deve seguire il set di regole specificato. Questo è attualmente irrisolto ma almeno farà parte ufficialmente delle specifiche di JavaScript.

Questa domanda è molto più complicata di quanto sembri in superficie, e al di là del grado di retribuzione della maggior parte delle persone per quanto riguarda la conoscenza degli interni di Javascript.

La prototypeproprietà di un oggetto viene utilizzata durante la creazione di nuovi oggetti figlio di tale oggetto. La modifica non si riflette nell'oggetto stesso, piuttosto si riflette quando quell'oggetto viene utilizzato come costruttore per altri oggetti e non ha alcuna utilità nel modificare il prototipo di un oggetto esistente.

function myFactory(){};
myFactory.prototype = someOtherObject;

var newChild = new myFactory;
newChild.__proto__ === myFactory.prototype === someOtherObject; //true

Gli oggetti hanno una proprietà interna [[prototype]] che punta al prototipo corrente. Il modo in cui funziona è che ogni volta che viene chiamata una proprietà su un oggetto, inizierà dall'oggetto e poi risalirà attraverso la catena [[prototype]] finché non trova una corrispondenza, o fallisce, dopo il prototipo dell'oggetto radice. Questo è il modo in cui Javascript consente la creazione runtime e la modifica degli oggetti; ha un piano per cercare ciò di cui ha bisogno.

La __proto__proprietà esiste in alcune implementazioni (molte ora): qualsiasi implementazione di Mozilla, tutte quelle webkit che conosco, alcune altre. Questa proprietà punta alla proprietà interna [[prototype]] e consente la modifica post-creazione sugli oggetti. Qualsiasi proprietà e funzione passerà istantaneamente per corrispondere al prototipo grazie a questa ricerca concatenata.

Questa funzionalità, sebbene sia ora standardizzata, non è ancora una parte richiesta di JavaScript e nei linguaggi che la supportano ha un'alta probabilità di abbattere il codice nella categoria "non ottimizzato". I motori JS devono fare del loro meglio per classificare il codice, specialmente il codice "caldo" a cui si accede molto spesso, e se stai facendo qualcosa di stravagante come la modifica __proto__, non ottimizzeranno affatto il tuo codice.

Questo post https://bugzilla.mozilla.org/show_bug.cgi?id=607863 discute specificamente delle attuali implementazioni __proto__e delle differenze tra di loro. Ogni implementazione lo fa in modo diverso, perché è un problema difficile e irrisolto. Tutto in Javascript è modificabile, tranne a.) La sintassi b.) Oggetti host (il DOM esiste al di fuori di Javascript tecnicamente) e c.) __proto__. Il resto è completamente nelle mani di te e di ogni altro sviluppatore, quindi puoi capire perché __proto__sporge come un pollice dolorante.

C'è una cosa che __proto__consente che altrimenti sarebbe impossibile: la designazione di un prototipo di oggetti in fase di esecuzione separato dal suo costruttore. Questo è un caso d'uso importante ed è uno dei motivi principali per cui __proto__non è già morto. È abbastanza importante che sia stato un serio punto di discussione nella formulazione di Harmony, o che presto sarà conosciuto come ECMAScript 6. La capacità di specificare il prototipo di un oggetto durante la creazione farà parte della prossima versione di Javascript e questa sarà la campana che indica __proto__i giorni è formalmente numerata.

A breve termine, puoi utilizzarlo __proto__se stai prendendo di mira i browser che lo supportano (non IE, e nessun IE lo farà mai). È probabile che funzionerà in webkit e moz per i prossimi 10 anni poiché ES6 non sarà finalizzato fino al 2013.

Brendan Eich - re: Approach of new Object methods in ES5 :

Spiacente, ma impostabile __proto__, a parte il caso d'uso dell'inizializzatore di oggetti (cioè, su un nuovo oggetto non ancora raggiungibile, analogo a Object.create di ES5), è un'idea terribile. Scrivo questo dopo averlo progettato e realizzato settable __proto__oltre 12 anni fa.

... la mancanza di stratificazione è un problema (considera i dati JSON con una chiave "__proto__"). E peggio ancora, la mutabilità significa che le implementazioni devono verificare la presenza di catene di prototipi ciclici per evitare ilooping. [sono richiesti controlli costanti per la ricorsione infinita]

Infine, la modifica di __proto__un oggetto esistente può rompere i metodi non generici nel nuovo oggetto prototipo, che non può funzionare sull'oggetto ricevente (diretto) il cui __proto__viene impostato. Questa è semplicemente una cattiva pratica, una forma di confusione di tipo intenzionale, in generale.


Eccellente ripartizione! Sebbene non sia esattamente la stessa cosa di un prototipo mutabile, ECMA Harmony probabilmente implementerà i proxy , che ti consentono di applicare funzionalità aggiuntive su oggetti particolari utilizzando un pattern catchall.
Nick Husher

2
Il problema di Brendan Eich è originario dei linguaggi di prototipazione nel loro insieme. Eventualmente fare __proto__ non-writable, configurablerimedierebbe a queste preoccupazioni costringendo l'utente a riconfigurare esplicitamente la proprietà. Alla fine, le cattive pratiche sono associate agli abusi delle capacità di una lingua, non alle capacità stesse. Writable __proto__ non è inaudito. Esistono molte altre proprietà scrivibili non enumerabili e, sebbene vi siano pericoli, esistono anche le migliori pratiche. La testa di un martello non deve essere rimossa semplicemente perché può essere usata impropriamente per ferire qualcuno.
Girevole

La modifica __proto__è necessaria, perché Object.create produrrà solo oggetti, non funzioni per esempio, né simboli, espressioni regolari, elementi DOM o altri oggetti host. Se desideri che i tuoi oggetti siano richiamabili o speciali in altri modi, ma cambi comunque la loro catena di prototipi, sei bloccato senza __proto__proxy o impostabili
user2451227

2
Sarebbe meglio se non fosse fatto con una proprietà magica, ma invece con Object.setPrototype, eliminando la __proto__preoccupazione " in JSON". Le altre preoccupazioni di Brendan Eich sono ridicole. Catene di prototipi ricorsivi o l'utilizzo di un prototipo con metodi inadeguati per l'oggetto dato sono errori del programmatore, non errori di linguaggio e non dovrebbero essere un fattore, e inoltre possono accadere con Object.create così come con liberamente impostabile __proto__.
user2451227

1
IE 10 supporta __proto__.
kzh


14

È possibile utilizzare constructorsu un'istanza di un oggetto per alterare il prototipo di un oggetto sul posto. Credo che questo sia quello che stai chiedendo di fare.

Ciò significa che se hai fooche è un'istanza di Foo:

function Foo() {}

var foo = new Foo();

Puoi aggiungere una proprietà bara tutte le istanze di Fooprocedendo come segue:

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

Ecco un violino che mostra il proof-of-concept: http://jsfiddle.net/C2cpw/ . Non sono molto sicuro di come se la caveranno i browser meno recenti con questo approccio, ma sono abbastanza sicuro che dovrebbe funzionare abbastanza bene.

Se la tua intenzione è combinare funzionalità in oggetti, questo snippet dovrebbe fare il lavoro:

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: Questo è informativo e interessante, ma non mi aiuta davvero a ottenere ciò che voglio. Sto aggiornando la mia domanda per essere più specifica.
Vivian River

Puoi anche usarefoo.__proto__.bar = 'bar';
Jam Risser

9

Puoi farlo foo.__proto__ = FooClass.prototype, AFAIK che è supportato da Firefox, Chrome e Safari. Tieni presente che la __proto__proprietà non è standard e potrebbe scomparire a un certo punto.

Documentazione: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Vedi anche http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html per una spiegazione del motivo per cui non c'è Object.setPrototypeOf()e perché __proto__è obsoleto.


3

È possibile definire la funzione di costruzione del proxy e quindi creare una nuova istanza e copiare tutte le proprietà dall'oggetto originale ad essa.

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

Demo dal vivo: http://jsfiddle.net/6Xq3P/

Il Customcostruttore rappresenta il nuovo prototipo, ergo, il suo Custom.prototypeoggetto contiene tutte le nuove proprietà che vorresti usare con il tuo oggetto originale.

All'interno del Customcostruttore, copi semplicemente tutte le proprietà dall'oggetto originale al nuovo oggetto istanza.

Questo nuovo oggetto istanza contiene tutte le proprietà dell'oggetto originale (sono state copiate in esso all'interno del costruttore), e anche tutte le nuove proprietà definite all'interno Custom.prototype(perché il nuovo oggetto è Customun'istanza).


3

Non è possibile modificare il prototipo di un oggetto JavaScript che è già stato istanziato in modo cross browser. Come altri hanno già detto, le tue opzioni includono:

  1. cambiando il non standard / cross browser __proto__di proprietà
  2. Copia le proprietà degli oggetti in un nuovo oggetto

Nessuno dei due è particolarmente eccezionale, soprattutto se devi eseguire il ciclo ricorsivo di un oggetto in oggetti interni per modificare efficacemente un intero prototipo di elementi.

Soluzione alternativa alla domanda

Darò uno sguardo più astratto alla funzionalità che sembra che desideri.

Fondamentalmente prototipo / metodi consentono solo un modo per raggruppare le funzioni in base a un oggetto.
Invece di scrivere

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

Scrivi

'   test  '.trim();

La sintassi precedente è stata coniata come termine OOP a causa della sintassi object.method (). Alcuni dei principali vantaggi dell'OOP rispetto alla programmazione funzionale tradizionale includono:

  1. Nomi di metodi brevi e meno variabili obj.replace('needle','replaced')rispetto al dover ricordare nomi come str_replace ( 'foo' , 'bar' , 'subject')e la posizione delle diverse variabili
  2. metodo chaining ( string.trim().split().join()) è potenzialmente più facile da modificare e scrivere quindi funzioni annidatejoin(split(trim(string))

Sfortunatamente in JavaScript (come mostrato sopra) non è possibile modificare un prototipo già esistente. Idealmente sopra potresti modificare Object.prototypesolo per l'oggetto specificato sopra, ma sfortunatamente la modifica Object.prototypepotrebbe potenzialmente rompere gli script (con conseguente collisione di proprietà e override).

Non esiste una via di mezzo comunemente usata tra questi 2 stili di programmazione e nessun modo OOP per organizzare le funzioni personalizzate.

UnlimitJS fornisce una via di mezzo che ti consente di definire metodi personalizzati. Evita:

  1. Collisione di proprietà, perché non estende i prototipi degli Oggetti
  2. Permette ancora una sintassi di concatenamento OOP
  3. È uno script cross browser da 450 byte (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) che Unlimit è la maggior parte dei problemi di collisione delle proprietà del prototipo di JavaScript

Usando il codice sopra, creerei semplicemente uno spazio dei nomi di funzioni che intendi chiamare sull'oggetto.

Ecco un esempio:

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

Puoi leggere altri esempi qui UnlimitJS . Fondamentalmente quando si chiama [Unlimit]()una funzione, consente di chiamare la funzione come metodo su un oggetto. È come una via di mezzo tra l'OOP e le strade funzionali.


2

Non è possibile modificare il [[prototype]]riferimento di oggetti già costruiti, per quanto ne so. Potresti alterare la proprietà prototipo della funzione costruttore originale ma, come hai già commentato, quel costruttore lo è Object, e alterare i costrutti JS principali è una brutta cosa.

Tuttavia, potresti creare un oggetto proxy dell'oggetto costruito che implementa le funzionalità aggiuntive di cui hai bisogno. È anche possibile eseguire il monkeypatch dei metodi e comportamenti aggiuntivi assegnandoli direttamente all'oggetto in questione.

Forse puoi ottenere ciò che desideri in qualche altro modo, se sei disposto ad avvicinarti da un'angolazione diversa: cosa devi fare per fare confusione con il prototipo?


Qualcun altro può commentare l'accuratezza di questo?
Vivian River

Sulla base di questo terribile jsfiddle sembra che tu possa alterare il prototipo di un oggetto esistente alterandone le __proto__proprietà. Funzionerà solo nei browser che supportano la __proto__notazione, che è Chrome e Firefox, ed è stato deprecato . Quindi, in breve, puoi cambiare il valore [[prototype]]di un oggetto, ma probabilmente non dovresti.
Nick Husher

1

Se conosci il prototipo, perché non iniettarlo nel codice?

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

Quindi, una volta serializzati i dati, ottieni

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

ora hai solo bisogno di un costruttore che prenda un array come argomento.


0

Non c'è modo di ereditare realmente da esso Arrayo di "sottoclassarlo".

Quello che puoi fare è questo ( ATTENZIONE: CODICE FESTERING IN AVANTI ):

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

Funziona, ma causerà alcuni problemi a chiunque attraversi il suo percorso (sembra un array, ma le cose andranno storte se provi a manipolarlo).

Perché non segui la strada giusta e aggiungi metodi / proprietà direttamente fooo usi un costruttore e salvi il tuo array come proprietà?

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

se vuoi creare prototipi al volo, questo è uno dei modi

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

No, non puoi. Questa è una proprietà statica.
SLaks

@SLaks prototypeè statico? Non sono sicuro di cosa intendi.
Šime Vidas

1
prototypeè una proprietà di una funzione, non un oggetto. Object.prototypeesiste; {}.prototypenon lo fa.
SLaks

Se non ti interessa la compatibilità del browser, puoi usare Object.getPrototypeOf(foo)per ottenere l'oggetto prototipo del costruttore. È possibile modificare le proprietà su quello per modificare il prototipo dell'oggetto. Funziona solo con i browser recenti, ma non saprei dirti quali.
Nick Husher

@TeslaNick: potresti semplicemente andare a modificare Object.prototypedirettamente perché è molto probabile che Object.getPrototypeOf(foo)tornerà. Non molto utile.
Wladimir Palant
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.