Wie setze ich den Prototyp eines bereits instanziierten JavaScript-Objekts?

106

Angenommen, ich habe ein Objekt fooin meinem JavaScript-Code. fooist ein komplexes Objekt und wird woanders erzeugt. Wie kann ich den Prototyp des fooObjekts ändern ?

Meine Motivation besteht darin, geeignete Prototypen für Objekte festzulegen, die von .NET in JavaScript-Literale serialisiert wurden.

Angenommen, ich habe den folgenden JavaScript-Code in eine ASP.NET-Seite geschrieben.

var foo = <%=MyData %>;

Angenommen, dies MyDataist das Ergebnis des Aufrufs von .NET JavaScriptSerializerfür ein Dictionary<string,string>Objekt.

Zur Laufzeit wird dies wie folgt:

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

Wie Sie sehen können, foowird eine Reihe von Objekten. Ich möchte foomit einem geeigneten Prototyp initialisieren können . Ich möchte das noch nicht ändern . Wie kann ich das machen?Object.prototypeArray.prototype

Vivian River
quelle
Möchten Sie den vorhandenen Prototyp ergänzen oder auf einen neuen Prototyp umstellen?
SLaks
Meinen Sie damit, Änderungen am Prototyp vorzunehmen - oder den Prototyp tatsächlich zu ändern, indem Sie einen Prototyp austauschen und durch einen anderen ersetzen. Ich bin mir nicht mal sicher, ob der spätere Fall möglich ist.
James Gaunt
2
Meinen Sie die explizite Prototyp-Eigenschaft oder den impliziten Prototyp-Link? (Diese beiden sind zwei sehr unterschiedliche Dinge)
Šime Vidas
Ich war ursprünglich damit beschäftigt: stackoverflow.com/questions/7013545/…
Vivian River
1
Kennen Sie Backbone extendoder Google goog.inherit? Viele Entwickler bieten Möglichkeiten zum Erstellen von Vererbungen an, bevor sie den älteren newKonstruktor aufrufen. Dies Object.creategeschah, bevor uns dies mitgeteilt wurde, und wir mussten uns nicht um das Überschreiben kümmern Object.prototype.
Ryan

Antworten:

114

EDIT Feb. 2012: Die Antwort unten ist nicht mehr korrekt. __proto__ wird zu ECMAScript 6 als "normativ optional" hinzugefügt, was bedeutet, dass es nicht implementiert werden muss, aber wenn dies der Fall ist, muss es den vorgegebenen Regeln folgen. Dies ist derzeit ungelöst, wird aber zumindest offiziell Teil der JavaScript-Spezifikation sein.

Diese Frage ist viel komplizierter, als es auf den ersten Blick scheint, und übersteigt die Gehaltsstufe der meisten Menschen in Bezug auf das Wissen über Javascript-Interna.

Die prototypeEigenschaft eines Objekts wird verwendet, wenn neue untergeordnete Objekte dieses Objekts erstellt werden. Das Ändern spiegelt sich nicht im Objekt selbst wider, sondern wird reflektiert, wenn dieses Objekt als Konstruktor für andere Objekte verwendet wird, und hat keinen Sinn beim Ändern des Prototyps eines vorhandenen Objekts.

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

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

Objekte haben eine interne Eigenschaft, die auf den aktuellen Prototyp verweist. Wenn eine Eigenschaft für ein Objekt aufgerufen wird, beginnt sie am Objekt und durchläuft dann die [[Prototyp]] - Kette, bis sie nach dem Stammobjekt-Prototyp eine Übereinstimmung findet oder fehlschlägt. Auf diese Weise ermöglicht Javascript das Erstellen und Ändern von Objekten zur Laufzeit. Es hat einen Plan für die Suche nach dem, was es braucht.

Die __proto__Eigenschaft ist in einigen Implementierungen vorhanden (viel jetzt): in jeder Mozilla-Implementierung, in allen mir bekannten Webkit-Implementierungen und in einigen anderen. Diese Eigenschaft verweist auf die interne Eigenschaft [[Prototyp]] und ermöglicht Änderungen nach der Erstellung von Objekten. Alle Eigenschaften und Funktionen werden aufgrund dieser verketteten Suche sofort auf den Prototyp umgeschaltet.

Diese Funktion ist zwar jetzt standardisiert, aber immer noch kein erforderlicher Bestandteil von JavaScript. In unterstützenden Sprachen besteht eine hohe Wahrscheinlichkeit, dass Ihr Code in die Kategorie "nicht optimiert" eingestuft wird. JS-Engines müssen ihr Bestes geben, um Code zu klassifizieren, insbesondere "heißen" Code, auf den sehr oft zugegriffen wird, und wenn Sie etwas Besonderes wie das Ändern tun __proto__, werden sie Ihren Code überhaupt nicht optimieren.

In diesem Beitrag https://bugzilla.mozilla.org/show_bug.cgi?id=607863 werden speziell die aktuellen Implementierungen __proto__und deren Unterschiede erläutert . Jede Implementierung macht es anders, weil es ein hartes und ungelöstes Problem ist. Alles in Javascript ist veränderbar, außer a.) Die Syntax b.) Hostobjekte (das DOM existiert technisch außerhalb von Javascript) und c.) __proto__. Der Rest liegt vollständig in Ihren Händen und bei jedem anderen Entwickler, sodass Sie sehen können, warum __proto__er wie ein schmerzender Daumen hervorsteht.

Es gibt eine Sache, __proto__die dies ansonsten unmöglich macht: die Bezeichnung eines Objektprototyps zur Laufzeit getrennt von seinem Konstruktor. Dies ist ein wichtiger Anwendungsfall und einer der Hauptgründe, warum er __proto__noch nicht tot ist. Es ist wichtig genug, dass dies ein ernstzunehmender Diskussionspunkt bei der Formulierung von Harmony war oder bald als ECMAScript 6 bekannt sein wird. Die Möglichkeit, den Prototyp eines Objekts während der Erstellung anzugeben, wird Teil der nächsten Version von Javascript sein Die Glocke, die __proto__die Tage anzeigt , ist offiziell nummeriert.

Kurzfristig können Sie verwenden, __proto__wenn Sie auf Browser abzielen, die dies unterstützen (nicht IE und kein IE wird dies jemals tun). Es ist wahrscheinlich, dass es in den nächsten 10 Jahren in Webkit und Moz funktioniert, da ES6 erst 2013 fertiggestellt wird.

Brendan Eich - re: Ansatz neuer Objektmethoden in ES5 :

Sorry, ... aber einstellbar __proto__, abgesehen vom Anwendungsfall des Objektinitialisierers (dh bei einem neuen Objekt, das noch nicht erreichbar ist, analog zu Object.create von ES5), ist eine schreckliche Idee. Ich schreibe dies, nachdem ich es vor __proto__über 12 Jahren entworfen und implementiert habe.

... das Fehlen einer Schichtung ist ein Problem (betrachten Sie JSON-Daten mit einem Schlüssel "__proto__"). Schlimmer noch, die Veränderlichkeit bedeutet, dass Implementierungen nach zyklischen Prototypketten suchen müssen, um ein Iloooping zu vermeiden. [ständige Überprüfung auf unendliche Rekursion erforderlich]

Schließlich kann das Mutieren __proto__eines vorhandenen Objekts nicht generische Methoden im neuen Prototypobjekt beschädigen, die möglicherweise nicht auf dem Empfängerobjekt (direkt) funktionieren können, dessen Objekt __proto__festgelegt wird. Dies ist einfach eine schlechte Praxis, eine Form der absichtlichen Typverwirrung im Allgemeinen.

pangular
quelle
Hervorragende Aufteilung! Obwohl es nicht ganz dasselbe ist wie ein veränderlicher Prototyp, wird ECMA Harmony wahrscheinlich Proxys implementieren , mit denen Sie bestimmte Objekte mithilfe eines Catchall-Musters mit zusätzlichen Funktionen versehen können.
Nick Husher
2
Brendan Eichs Ausgabe stammt ursprünglich aus Prototyping-Sprachen als Ganzes. Möglicherweise non-writable, configurablewürde __proto__ diese Bedenken beseitigen, indem der Benutzer gezwungen wird, die Eigenschaft explizit neu zu konfigurieren. Am Ende sind schlechte Praktiken mit dem Missbrauch der Fähigkeiten einer Sprache verbunden, nicht mit den Fähigkeiten selbst. Beschreibbares __proto__ ist keine Seltenheit. Es gibt viele andere nicht aufzählbare beschreibbare Eigenschaften, und obwohl es Gefahren gibt, gibt es auch Best Practices. Der Kopf eines Hammers sollte nicht einfach entfernt werden, weil er nicht ordnungsgemäß zum Verwunden von Personen verwendet werden kann.
Swivel
Eine Mutation __proto__ist erforderlich, da Object.create nur Objekte erzeugt, keine Funktionen, noch Symbole, Regexes, DOM-Elemente oder andere Hostobjekte. Wenn Sie möchten, dass Ihre Objekte aufrufbar oder auf andere Weise speziell sind, aber dennoch ihre Prototypenkette ändern, stecken Sie ohne __proto__Einstellbarkeit oder Proxies fest
user2451227
2
Es wäre besser, wenn dies nicht mit einer magischen Eigenschaft, sondern mit Object.setPrototype geschehen würde, wodurch das Problem " __proto__in JSON" beseitigt würde . Brendan Eichs andere Sorgen sind lächerlich. Rekursive Prototypketten oder die Verwendung eines Prototyps mit unzureichenden Methoden für das angegebene Objekt sind Programmiererfehler, keine Sprachfehler und sollten kein Faktor sein. Außerdem können sie mit Object.create genauso gut wie mit frei einstellbaren auftreten __proto__.
user2451227
1
IE 10 unterstützt __proto__.
kzh
14

Sie können constructoreine Instanz eines Objekts verwenden, um den Prototyp eines Objekts an Ort und Stelle zu ändern. Ich glaube, das ist es, worum Sie bitten.

Dies bedeutet, wenn Sie welche haben foo, ist eine Instanz von Foo:

function Foo() {}

var foo = new Foo();

Sie können barallen Instanzen von eine Eigenschaft hinzufügen, Fooindem Sie folgende Schritte ausführen:

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

Hier ist eine Geige, die den Proof-of-Concept zeigt: http://jsfiddle.net/C2cpw/ . Ich bin mir nicht sicher, wie ältere Browser mit diesem Ansatz abschneiden werden, aber ich bin mir ziemlich sicher, dass dies den Job ziemlich gut machen sollte.

Wenn Sie die Funktionalität in Objekte mischen möchten, sollte dieses Snippet die folgende Aufgabe erfüllen:

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;
    }
  };
};
Tim
quelle
1
+1: Das ist informativ und interessant, hilft mir aber nicht wirklich, das zu bekommen, was ich will. Ich aktualisiere meine Frage, um genauer zu sein.
Vivian River
Sie könnten auch verwendenfoo.__proto__.bar = 'bar';
Jam Risser
9

Sie können foo.__proto__ = FooClass.prototypeAFAIK, das von Firefox, Chrome und Safari unterstützt wird. Beachten Sie, dass die __proto__Unterkunft nicht dem Standard entspricht und möglicherweise irgendwann verschwindet.

Dokumentation: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Unter http://www.mail-archive.com/[email protected]/msg00392.html finden Sie auch eine Erklärung, warum es keine gibt Object.setPrototypeOf()und warum sie __proto__veraltet ist.

Wladimir Palant
quelle
3

Sie können Ihre Proxy-Konstruktorfunktion definieren, dann eine neue Instanz erstellen und alle Eigenschaften vom ursprünglichen Objekt darauf kopieren.

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

Live-Demo: http://jsfiddle.net/6Xq3P/

Der CustomKonstruktor stellt den neuen Prototyp dar, also Custom.prototypeenthält sein Objekt alle neuen Eigenschaften, die Sie mit Ihrem ursprünglichen Objekt verwenden möchten.

Innerhalb des CustomKonstruktors kopieren Sie einfach alle Eigenschaften vom ursprünglichen Objekt in das neue Instanzobjekt.

Dieses neue Instanzobjekt enthält alle Eigenschaften des ursprünglichen Objekts (sie wurden im Konstruktor darauf kopiert) sowie alle darin definierten neuen Eigenschaften Custom.prototype(da das neue Objekt eine CustomInstanz ist).

Šime Vidas
quelle
3

Sie können den Prototyp eines JavaScript-Objekts nicht ändern, das bereits browserübergreifend instanziiert wurde. Wie andere bereits erwähnt haben, stehen folgende Optionen zur Verfügung:

  1. Ändern der Nicht-Standard- / Cross-Browser- __proto__Eigenschaft
  2. Kopieren Sie die Objekteigenschaften in ein neues Objekt

Beides ist nicht besonders gut, insbesondere wenn Sie ein Objekt rekursiv in innere Objekte schleifen müssen, um den gesamten Prototyp eines Elements effektiv zu ändern.

Alternative Lösung zur Frage

Ich werde einen abstrakteren Blick auf die Funktionalität werfen, die Sie sich wünschen.

Grundsätzlich ermöglichen Prototypen / Methoden nur die Möglichkeit, Funktionen basierend auf einem Objekt zu gruppieren.
Anstatt zu schreiben

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

du schreibst

'   test  '.trim();

Die obige Syntax wurde aufgrund der object.method () -Syntax als OOP bezeichnet. Einige der Hauptvorteile von OOPs gegenüber der herkömmlichen funktionalen Programmierung sind:

  1. Kurze Methodennamen und weniger Variablen, obj.replace('needle','replaced')anstatt sich Namen wie str_replace ( 'foo' , 'bar' , 'subject')und die Position der verschiedenen Variablen merken zu müssen
  2. method chaining ( string.trim().split().join()) ist möglicherweise einfacher zu ändern und zu schreiben als verschachtelte Funktionenjoin(split(trim(string))

Leider können Sie in JavaScript (wie oben gezeigt) einen bereits vorhandenen Prototyp nicht ändern. Im Idealfall können Sie oben Object.prototypenur für das oben angegebene Objekt Änderungen vornehmen. Durch Änderungen können Object.prototypejedoch möglicherweise Skripte beschädigt werden (was zu einer Kollision von Eigenschaften und zum Überschreiben führt).

Es gibt keinen häufig verwendeten Mittelweg zwischen diesen beiden Programmierstilen und keine OOP-Methode zum Organisieren benutzerdefinierter Funktionen.

UnlimitJS bietet einen Mittelweg, mit dem Sie benutzerdefinierte Methoden definieren können. Es vermeidet:

  1. Eigenschaftskollision, da sie die Prototypen von Objekten nicht erweitert
  2. Ermöglicht weiterhin eine OOP-Verkettungssyntax
  3. Es handelt sich um ein 450-Byte-Cross-Browser-Skript (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+), das Unlimits Hauptprobleme bei der Kollision von Prototyp-Eigenschaften von JavaScript verursacht

Mit Ihrem obigen Code würde ich einfach einen Namespace von Funktionen erstellen, die Sie für das Objekt aufrufen möchten.

Hier ist ein Beispiel:

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

Weitere Beispiele finden Sie hier UnlimitJS . Wenn Sie [Unlimit]()eine Funktion aufrufen , kann die Funktion grundsätzlich als Methode für ein Objekt aufgerufen werden. Es ist wie ein Mittelweg zwischen der OOP und funktionalen Straßen.

Limette
quelle
2

[[prototype]]Soweit ich weiß, können Sie die Referenz bereits erstellter Objekte nicht ändern . Sie können die Prototyp-Eigenschaft der ursprünglichen Konstruktorfunktion ändern, aber wie Sie bereits kommentiert haben, ist dies der Konstruktor Object, und das Ändern von JS-Kernkonstrukten ist eine schlechte Sache.

Sie können jedoch ein Proxy-Objekt des erstellten Objekts erstellen, das die zusätzlichen Funktionen implementiert, die Sie benötigen. Sie können die zusätzlichen Methoden und Verhaltensweisen auch monkeypatchen, indem Sie sie direkt dem betreffenden Objekt zuweisen.

Vielleicht können Sie das, was Sie wollen, auf andere Weise bekommen, wenn Sie bereit sind, sich aus einem anderen Blickwinkel zu nähern: Was müssen Sie tun, um mit dem Prototyp herumzuspielen?

Nick Husher
quelle
Kann noch jemand die Richtigkeit kommentieren?
Vivian River
Basierend auf dieser schrecklichen Geige scheint es, dass Sie den Prototyp eines vorhandenen Objekts ändern können, indem Sie seine __proto__Eigenschaft ändern . Dies funktioniert nur in Browsern, die die __proto__Notation Chrome und Firefox unterstützen, und sie ist veraltet . Kurz gesagt, Sie können das [[prototype]]Objekt ändern , sollten es aber wahrscheinlich nicht.
Nick Husher
1

Wenn Sie den Prototyp kennen, warum nicht ihn in den Code einfügen?

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

Sobald die Daten serialisiert sind, erhalten Sie

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

Jetzt brauchen Sie nur noch einen Konstruktor, der ein Array als Argument verwendet.

umgeschrieben
quelle
0

Es gibt keine Möglichkeit, es wirklich zu erben Arrayoder "unterzuordnen".

Was Sie tun können, ist Folgendes ( WARNUNG: 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

Dies funktioniert, verursacht jedoch gewisse Probleme für jeden, der seinen Pfad kreuzt (es sieht aus wie ein Array, aber es wird schief gehen, wenn Sie versuchen, es zu manipulieren).

Warum gehen Sie nicht den richtigen Weg und fügen Methoden / Eigenschaften direkt hinzu foooder verwenden einen Konstruktor und speichern Ihr Array als Eigenschaft?

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"}]
Ricardo Tomasi
quelle
0

Wenn Sie einen Prototyp im laufenden Betrieb erstellen möchten, ist dies eine der Möglichkeiten

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);
Yene Mulatu
quelle
-1
foo.prototype.myFunction = function(){alert("me");}
PhD
quelle
Nein, das kannst du nicht. Das ist eine statische Eigenschaft.
SLaks
@SLaks prototypeist statisch? Ich bin mir nicht sicher was du meinst.
Šime Vidas
1
prototypeist eine Eigenschaft einer Funktion, kein Objekt. Object.prototypeexistiert; {}.prototypenicht.
SLaks
Wenn Sie sich nicht für die Browserkompatibilität interessieren, können Object.getPrototypeOf(foo)Sie das Konstruktorprototypobjekt abrufen. Sie können die Eigenschaften dafür ändern, um den Prototyp des Objekts zu ändern. Es funktioniert nur in neueren Browsern, kann Ihnen jedoch nicht sagen, welche.
Nick Husher
@TeslaNick: Sie können einfach Object.prototypedirekt Änderungen vornehmen, da dies höchstwahrscheinlich der Fall ist Object.getPrototypeOf(foo). Nicht sehr nützlich.
Wladimir Palant