Jak ustawić prototyp obiektu JavaScript, który został już utworzony?

106

Załóżmy, że mam obiekt foow swoim kodzie JavaScript. foojest złożonym obiektem i jest generowany gdzie indziej. Jak mogę zmienić prototyp fooobiektu?

Moją motywacją jest ustawienie odpowiednich prototypów obiektów serializowanych z .NET do literałów JavaScript.

Załóżmy, że napisałem następujący kod JavaScript na stronie ASP.NET.

var foo = <%=MyData %>;

Załóżmy, że MyDatajest to wynikiem wywołania .NET JavaScriptSerializerna Dictionary<string,string>obiekcie.

W czasie wykonywania wygląda to następująco:

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

Jak widać, foostaje się tablicą obiektów. Chciałbym mieć możliwość zainicjowania fooodpowiedniego prototypu. Ja nie chce modyfikować Object.prototypeani Array.prototype. W jaki sposób mogę to zrobić?

Rzeka Vivian
źródło
Czy chcesz dodać do istniejącego prototypu lub przełączyć go na nowy prototyp?
SLaks
Czy masz na myśli wprowadzenie zmian w prototypie - czy faktycznie zmień prototyp, tak jak w przypadku wymiany jednego prototypu i zastąpienia go innym. Nie jestem nawet pewien, czy późniejszy przypadek jest możliwy.
James Gaunt
2
Czy masz na myśli jawną właściwość prototypu czy niejawne łącze prototypu? (Te dwie bardzo różne rzeczy)
Šime Vidas
Początkowo byłem tym zaniepokojony: stackoverflow.com/questions/7013545/…
Vivian River
1
Czy znasz Backbone extendlub Google goog.inherit? Wielu programistów zapewnia sposoby budowania dziedziczenia przed wywołaniem starszego newkonstruktora - czyli zanim otrzymaliśmy Object.createi nie musieli martwić się o nadpisanie Object.prototype.
Ryan

Odpowiedzi:

114

EDYCJA Luty 2012: poniższa odpowiedź nie jest już aktualna. __proto__ jest dodawany do ECMAScript 6 jako „normatywny opcjonalny”, co oznacza, że ​​nie jest wymagany do zaimplementowania, ale jeśli jest, musi być zgodny z podanym zestawem reguł. Obecnie jest to nierozwiązane, ale przynajmniej będzie oficjalnie częścią specyfikacji JavaScript.

To pytanie jest o wiele bardziej skomplikowane, niż się wydaje na pierwszy rzut oka, i wykracza poza poziom płac większości ludzi, jeśli chodzi o znajomość wewnętrznych elementów Javascript.

prototypeWłaściwość obiektu jest używany przy tworzeniu nowych obiektów podrzędnych tego obiektu. Zmiana nie odbija się w samym obiekcie, a raczej jest odzwierciedlana, gdy ten obiekt jest używany jako konstruktor dla innych obiektów i nie ma pożytku ze zmiany prototypu istniejącego obiektu.

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

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

Obiekty mają wewnętrzną właściwość [[prototyp]], która wskazuje na bieżący prototyp. Działa to tak, że za każdym razem, gdy wywoływana jest właściwość obiektu, zaczyna się ona od obiektu, a następnie przechodzi w górę przez łańcuch [[prototyp]], aż znajdzie dopasowanie lub niepowodzenie po głównym prototypie obiektu. W ten sposób Javascript umożliwia budowanie i modyfikowanie obiektów w środowisku wykonawczym; ma plan poszukiwania tego, czego potrzebuje.

Ta __proto__właściwość istnieje w niektórych implementacjach (obecnie dużo): we wszystkich implementacjach Mozilli, we wszystkich webkitach, które znam, w innych. Ta właściwość wskazuje na wewnętrzną właściwość [[prototyp]] i umożliwia modyfikację obiektów po utworzeniu. Wszelkie właściwości i funkcje zostaną natychmiast przełączone w celu dopasowania do prototypu dzięki wyszukiwaniu łańcuchowemu.

Ta funkcja, choć jest obecnie standaryzowana, nadal nie jest wymaganą częścią JavaScript, aw językach obsługujących ją istnieje duże prawdopodobieństwo, że kod zostanie umieszczony w kategorii „niezoptymalizowana”. Silniki JS muszą zrobić wszystko, co w ich mocy, aby sklasyfikować kod, szczególnie ten „gorący”, do którego dostęp jest bardzo często, a jeśli robisz coś wymyślnego, na przykład modyfikowanie __proto__, w ogóle nie zoptymalizują Twojego kodu.

Ten post https://bugzilla.mozilla.org/show_bug.cgi?id=607863 szczegółowo omawia bieżące implementacje __proto__i różnice między nimi. Każda implementacja robi to inaczej, ponieważ jest to trudny i nierozwiązany problem. Wszystko w Javascript jest zmienne, z wyjątkiem a.) Składni b.) Obiektów hosta (technicznie rzecz biorąc, DOM istnieje poza JavaScriptem) ic.) __proto__. Reszta jest całkowicie w rękach Ciebie i każdego innego programisty, więc możesz zobaczyć, dlaczego __proto__wystaje jak bolący kciuk.

Jest jedna rzecz, która __proto__pozwala na to, co w innym przypadku jest niemożliwe: wyznaczenie prototypu obiektów w czasie wykonywania, niezależnie od jego konstruktora. Jest to ważny przypadek użycia i jeden z głównych powodów, dla których __proto__nie jest martwy. Na tyle ważne, że był to poważny punkt dyskusji przy formułowaniu Harmony lub wkrótce będzie znany jako ECMAScript 6. Możliwość określenia prototypu obiektu podczas tworzenia będzie częścią kolejnej wersji Javascript i będzie to dzwon wskazujący __proto__dni są formalnie numerowane.

Na krótką metę możesz użyć, __proto__jeśli kierujesz reklamy na przeglądarki, które go obsługują (nie IE i żaden IE nigdy tego nie zrobi). Prawdopodobnie będzie działać w webkit i moz przez następne 10 lat, ponieważ ES6 nie zostanie sfinalizowany do 2013 roku.

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

Przepraszam, ale możliwe do __proto__ustalenia, poza przypadkiem użycia inicjatora obiektu (tj. Na nowym obiekcie nieosiągalnym jeszcze, analogicznie do Object.create w ES5), jest okropnym pomysłem. Piszę to po zaprojektowaniu i wdrożeniu ustawialnego __proto__ponad 12 lat temu.

... brak stratyfikacji jest problemem (rozważ dane JSON z kluczem "__proto__"). Co gorsza, zmienność oznacza, że ​​implementacje muszą sprawdzać cykliczne łańcuchy prototypów, aby uniknąć iloopingu. [wymagane są ciągłe sprawdzanie nieskończonej rekurencji]

Wreszcie, mutacja __proto__na istniejącym obiekcie może zepsuć nieogólne metody w nowym prototypowym obiekcie, który prawdopodobnie nie będzie działał na odbiorniku (bezpośrednim) obiekcie, który __proto__jest ustawiany. Jest to po prostu zła praktyka, ogólnie rzecz biorąc, rodzaj zamierzonego pomieszania.

charakterystyczny
źródło
Doskonała awaria! Chociaż nie jest to dokładnie to samo, co modyfikowalny prototyp, ECMA Harmony prawdopodobnie zaimplementuje serwery proxy , które pozwolą Ci na zastosowanie dodatkowych funkcji do określonych obiektów przy użyciu wzorca catchall.
Nick Husher
2
Problem Brendana Eicha pochodzi z całego języka prototypowania. Możliwe, że wykonanie __proto__ non-writable, configurablerozwiązałoby te problemy, zmuszając użytkownika do jawnej rekonfiguracji właściwości. Ostatecznie złe praktyki wiążą się z nadużywaniem możliwości języka, a nie samych możliwości. Zapisywalny __proto__ nie jest czymś niezwykłym. Istnieje wiele innych niewliczalnych właściwości zapisywalnych i chociaż istnieją zagrożenia, istnieją również najlepsze praktyki. Nie należy zdejmować łba młotka tylko dlatego, że może być niewłaściwie użyta do zranienia kogoś.
Obrót
Mutacja __proto__jest potrzebna, ponieważ Object.create będzie wytwarzać tylko obiekty, a nie funkcje, na przykład symbole, reguły, elementy DOM lub inne obiekty hosta. Jeśli chcesz, aby twoje obiekty były wywoływalne lub wyjątkowe w inny sposób, ale nadal zmieniasz ich łańcuch prototypów, utkniesz bez ustawiania __proto__lub proxy
user2451227
2
Byłoby lepiej, gdyby nie było to zrobione z magiczną właściwością, ale zamiast tego z Object.setPrototype, eliminując __proto__problem „ w JSON”. Inne obawy Brendana Eicha są śmieszne. Rekurencyjne łańcuchy prototypów lub użycie prototypu z nieadekwatnymi metodami do danego obiektu są błędami programisty, a nie błędami językowymi i nie powinny być czynnikiem, a poza tym mogą się zdarzyć z Object.create równie dobrze, jak z dowolnie ustawianymi __proto__.
user2451227
1
IE 10 obsługuje __proto__.
kzh
14

Możesz użyć constructorna instancji obiektu, aby zmienić prototyp obiektu w miejscu. Myślę, że o to właśnie prosisz.

Oznacza to, że jeśli masz, fooktóry jest wystąpieniem Foo:

function Foo() {}

var foo = new Foo();

Możesz dodać właściwość bardo wszystkich instancji programu Foo, wykonując następujące czynności:

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

Oto skrzypce pokazujące dowód słuszności koncepcji: http://jsfiddle.net/C2cpw/ . Nie jestem do końca pewien, jak starsze przeglądarki poradzą sobie z tym podejściem, ale jestem prawie pewien, że powinno to całkiem dobrze wykonać zadanie.

Jeśli Twoim zamiarem jest dodanie funkcji do obiektów, ten fragment powinien wystarczyć:

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
źródło
1
+1: To jest pouczające i interesujące, ale tak naprawdę nie pomaga mi uzyskać tego, czego chcę. Aktualizuję moje pytanie, aby było bardziej szczegółowe.
Vivian River
Możesz również użyćfoo.__proto__.bar = 'bar';
Jam Risser,
9

Możesz to zrobić foo.__proto__ = FooClass.prototype, AFAIK obsługiwany przez przeglądarki Firefox, Chrome i Safari. Należy pamiętać, że __proto__nieruchomość jest niestandardowa i może w pewnym momencie zniknąć.

Dokumentacja: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Zobacz też http://www.mail-archive.com/[email protected]/msg00392.html, aby uzyskać wyjaśnienie, dlaczego nie istnieje Object.setPrototypeOf()i dlaczego __proto__jest przestarzałe.

Wladimir Palant
źródło
3

Można zdefiniować funkcję konstruktora proxy, a następnie utworzyć nową instancję i skopiować do niej wszystkie właściwości z oryginalnego obiektu.

// 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 na żywo: http://jsfiddle.net/6Xq3P/

CustomKonstruktor reprezentuje nowy prototyp, ergo, jego Custom.prototypeprzedmiot zawiera wszystkie nowe właściwości których chcesz korzystać z oryginalnego obiektu.

Wewnątrz Customkonstruktora wystarczy skopiować wszystkie właściwości z oryginalnego obiektu do nowego obiektu instancji.

Ten nowy obiekt instancji zawiera wszystkie właściwości z oryginalnego obiektu (zostały do ​​niego skopiowane wewnątrz konstruktora), a także wszystkie nowe właściwości zdefiniowane wewnątrz Custom.prototype(ponieważ nowy obiekt jest Custominstancją).

Šime Vidas
źródło
3

Nie możesz zmienić prototypu obiektu JavaScript, którego instancja została już utworzona za pomocą różnych przeglądarek. Jak wspominali inni, dostępne opcje obejmują:

  1. zmieniając niestandardowe / cross przeglądarka __proto__nieruchomość
  2. Skopiuj właściwości obiektów do nowego obiektu

Żadne z nich nie są szczególnie świetne, zwłaszcza jeśli musisz rekurencyjnie przechodzić przez obiekt do obiektów wewnętrznych, aby skutecznie zmienić cały prototyp elementów.

Alternatywne rozwiązanie pytania

Przyjrzę się bardziej abstrakcyjnie na funkcjonalność, której pragniesz.

Zasadniczo prototyp / metody po prostu pozwalają na grupowanie funkcji na podstawie obiektu.
Zamiast pisać

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

ty piszesz

'   test  '.trim();

Powyższa składnia została stworzona jako OOP ze względu na składnię object.method (). Niektóre z głównych zalet OOP w porównaniu z tradycyjnym programowaniem funkcjonalnym obejmują:

  1. Krótkie nazwy metod i mniej zmiennych obj.replace('needle','replaced')a konieczność zapamiętywania nazw takich jak str_replace ( 'foo' , 'bar' , 'subject')i lokalizacji różnych zmiennych
  2. metoda chaining ( string.trim().split().join()) jest potencjalnie łatwiejszą do zmodyfikowania i zapisania niż funkcje zagnieżdżonejoin(split(trim(string))

Niestety w JavaScript (jak pokazano powyżej) nie można modyfikować już istniejącego prototypu. Najlepiej byłoby, gdyby powyżej można było modyfikować Object.prototypetylko powyższe obiekty, ale niestety modyfikowanie Object.prototypemogłoby potencjalnie zepsuć skrypty (powodując kolizję właściwości i nadpisywanie).

Nie ma powszechnie używanego środka pośredniego między tymi dwoma stylami programowania i nie ma sposobu OOP na organizację funkcji niestandardowych.

UnlimitJS zapewnia kompromis umożliwiający definiowanie niestandardowych metod. Unika:

  1. Kolizja własności, ponieważ nie rozszerza prototypów Objects
  2. Nadal pozwala na składnię łańcuchową OOP
  3. Jest to 450-bajtowy skrypt w różnych przeglądarkach (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+), z którym Unlimit ma wiele problemów z kolizjami właściwości prototypów JavaScript

Używając powyższego kodu, po prostu utworzyłbym przestrzeń nazw funkcji, które zamierzasz wywołać względem obiektu.

Oto przykład:

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

Więcej przykładów możesz przeczytać tutaj UnlimitJS . Zasadniczo, gdy wywołujesz [Unlimit]()funkcję, umożliwia wywołanie funkcji jako metody w obiekcie. To jak środek między OOP a funkcjonalnymi drogami.

Limonka
źródło
2

[[prototype]]O ile wiem, nie można zmienić odniesienia do już zbudowanych obiektów. Mógłbyś zmienić właściwość prototypu oryginalnej funkcji konstruktora, ale, jak już zauważyłeś, tym konstruktorem jest Object, a modyfikowanie podstawowych konstrukcji JS jest złą rzeczą.

Możesz jednak utworzyć obiekt proxy skonstruowanego obiektu, który implementuje dodatkową funkcjonalność, której potrzebujesz. Możesz także zastosować monkeypatch dodatkowe metody i zachowania, przypisując je bezpośrednio do danego obiektu.

Być może możesz dostać to, czego chcesz, w inny sposób, jeśli chcesz podejść z innego punktu widzenia: Co musisz zrobić, aby bawić się prototypem?

Nick Husher
źródło
Czy ktoś inny może skomentować dokładność tego?
Vivian River
Na podstawie tego okropnego jsfiddle wydaje się , że można zmienić prototyp istniejącego obiektu, zmieniając jego __proto__właściwości. Będzie to działać tylko w przeglądarkach obsługujących __proto__notację, czyli Chrome i Firefox, i zostało wycofane . Krótko mówiąc, możesz zmienić [[prototype]]obiekt, ale prawdopodobnie nie powinieneś.
Nick Husher
1

Jeśli znasz prototyp, dlaczego nie wstrzyknąć go w kodzie?

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

Tak więc, gdy dane zostaną zserializowane, otrzymasz

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

teraz potrzebujesz tylko konstruktora, który przyjmuje tablicę jako argument.

przepisany
źródło
0

Nie ma sposobu, aby tak naprawdę odziedziczyć Arraylub „podklasować” to.

Możesz to zrobić ( OSTRZEŻENIE: 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

To działa, ale spowoduje pewne problemy dla każdego, kto przecina jego ścieżkę (wygląda jak tablica, ale jeśli spróbujesz nią manipulować, wszystko pójdzie nie tak).

Dlaczego nie pójdziesz rozsądną drogą i nie dodasz metod / właściwości bezpośrednio do foolub nie użyjesz konstruktora i nie zapiszesz swojej tablicy jako właściwości?

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
źródło
0

jeśli chcesz stworzyć prototyp w locie, to jest jedna z możliwości

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
źródło
-1
foo.prototype.myFunction = function(){alert("me");}
PhD
źródło
Nie, nie możesz. To właściwość statyczna.
SLaks
@SLaks prototypejest statyczny? Nie jestem pewny co masz na myśli.
Šime Vidas
1
prototypejest właściwością funkcji, a nie obiektem. Object.prototypeistnieje; {}.prototypenie.
SLaks
Jeśli nie zależy ci na kompatybilności przeglądarki, możesz użyć Object.getPrototypeOf(foo)do pobrania prototypowego obiektu konstruktora. Możesz modyfikować właściwości, aby zmodyfikować prototyp obiektu. Działa tylko w najnowszych przeglądarkach, ale nie można powiedzieć, które z nich.
Nick Husher
@TeslaNick: Możesz po prostu przejść i zmodyfikować Object.prototypebezpośrednio, ponieważ najprawdopodobniej to Object.getPrototypeOf(foo)powróci. Niezbyt przydatne.
Wladimir Palant