2012-04-14 17 views
9

Próbuję sklonować obiekt w JavaScript. Stworzyłem własną "klasę", która ma funkcje prototypowe.Sklonowany obiekt JavaScript traci funkcje prototypowe

Mój problem: Kiedy klonuję obiekt, klon nie może uzyskać dostępu/wywołać żadnych funkcji prototypu.

otrzymuję komunikat o błędzie, gdy idę na dostęp do funkcji prototyp klonu:

clone.render nie jest funkcją

możesz mi powiedzieć jak mogę sklonować obiektu i zachować jego funkcje prototypowe

Ten prosty JSFiddle pokazuje błąd pojawia się: http://jsfiddle.net/VHEFb/1/

function cloneObject(obj) 
{ 
    // Handle the 3 simple types, and null or undefined 
    if (null == obj || "object" != typeof obj) return obj; 

    // Handle Date 
    if (obj instanceof Date) { 
    var copy = new Date(); 
    copy.setTime(obj.getTime()); 
    return copy; 
    } 

    // Handle Array 
    if (obj instanceof Array) { 
    var copy = []; 
    for (var i = 0, len = obj.length; i < len; ++i) { 
     copy[i] = cloneObject(obj[i]); 
    } 
    return copy; 
    } 

    // Handle Object 
    if (obj instanceof Object) { 
    var copy = {}; 
    for (var attr in obj) { 
     if (obj.hasOwnProperty(attr)) copy[attr] = cloneObject(obj[attr]); 
    } 
    return copy; 
    } 

    throw new Error("Unable to copy obj! Its type isn't supported."); 
} 

function MyObject(name) 
{ 
    this.name = name; 
    // I have arrays stored in this object also so a simple cloneNode(true) call wont copy those 
    // thus the need for the function cloneObject(); 
} 

MyObject.prototype.render = function() 
{ 
    alert("Render executing: "+this.name); 
} 

var base = new MyObject("base"); 
var clone = cloneObject(base); 
clone.name = "clone"; 
base.render(); 
clone.render(); // Error here: "clone.render is not a function" 
+0

widziałem [to rozwiązanie] (http://stackoverflow.com/a/728694/575527) tutaj w SO . nie ma jednak jasnego sposobu, aby dokładnie sklonować obiekt. [ten używa jQuery] (http: // stackoverflow.com/q/122102/575527) chociaż – Joseph

Odpowiedz

7

kilka uwag na temat kodu:

> if (obj instanceof Date) { 
>  var copy = new Date(); 
>  copy.setTime(obj.getTime()); 

może być:

if (obj instanceof Date) { 
    var copy = new Date(obj); 

i

> if (obj instanceof Array) { 

zwróci wartość false, jeśli obiekt jest tablicą z innego kontekstu globalnego, takiego jak iFrame. Rozważmy:

 if (o && !(o.constructor.toString().indexOf("Array") == -1)) 

>  var copy = []; 
>  for (var i = 0, len = obj.length; i < len; ++i) { 
>   copy[i] = cloneObject(obj[i]); 
>  } 

Kopiowanie indeksów jednej tablicy do drugiej może być bardziej efektywnie i precyzyjnie wykonane przy użyciu slice:

 var copy = obj.slice(); 

chociaż można przegapić żadnych innych właściwości, które mogły zostać dodane, które nie są numeryczne. Zapętlenie od 0 do długości doda właściwości do klonu, który nie istnieje w rzadkiej tablicy (na przykład elacje staną się niezdefiniowanymi elementami).

Co do klonowania części ...

we właściwościach kopiujących część, która będzie skopiować wszystkie właściwości, w tym na oryginalnym za [[Prototype]] łańcuchu, bezpośrednio do „klon” obiektu. Jedynym sposobem na naprawdę "klonowanie" obiektu jest ustawienie jego [[Prototype]] na ten sam obiekt co oryginał, a następnie skopiowanie właściwości przeliczalnych na oryginale (przefiltrowanym przez hasOwnProperty) do klonu.

Druga część jest trywialne, pierwsza część nie jest (w sensie ogólnym), ponieważ nie można zagwarantować, że nieruchomość konstruktor obiektu za odwołuje się obiekt, którego prototype jest jego [[Prototype]], nie można zagwarantować, że prototyp konstruktora hasn w międzyczasie zmieniono (tj. jest inny obiekt).

najbliżej można dostać się do korzystania Lasse Reichstein Nielsen's clone (spopularyzowany przez Douglasa Crockforda jak beget), który sprawia, że ​​oryginalny obiekt [[Prototype]] z klonu, a następnie ustawić konstruktora do tego samego obiektu. Chociaż prawdopodobnie nadal trzeba skopiować nad przeliczalnymi własnymi właściwościami, aby zamaskować właściwości o tej samej nazwie.

Tak naprawdę można klonować tylko w ograniczonym kontekście, nie można tego zrobić ogólnie. I ogólnie rzecz biorąc, ta realizacja prowadzi do projektu, w którym nie trzeba generalnie klonować obiektów.

+0

Teraz widzę (lepiej) twój punkt. Nadal byłbym zainteresowany referencjami dotyczącymi żonglerki prototypowej. Czy masz? – Eineki

+0

ES5 wprowadziło [Object.getPrototypeOf()] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf), które mogą być używane z 'Object.create()', ale nie jest dobrze obsługiwane jeszcze. Być może z testowaniem funkcji dla przestarzałego [\ _ \ _ proto__] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/Proto) możesz objąć dużą liczbę przeglądarek, ale będziesz tęsknić przeglądarki pre-ES5, takie jak IE 8 i wcześniejsze oraz zilion innych drobnych aplikacji użytkownika w telefonach, konsolach do gier itp. Sprawdź [Tabela kompatybilności Kangaxa] (http://kangax.github.com/es5-compat-table/). – RobG

3

Zamiast

var copy = {}; 

użycie

var copy = new obj.constructor; 
+0

Początkowo zasugerowałem to samo rozwiązanie, ale w drugiej chwili myślę, że to nie jest droga, co robi konstruktor, zmienia jego zachowanie za każdym razem, gdy się je nazywa, myśl o klasie, która tworzy alternatywne rzędy tabeli z innym łańcuchem prototypów. Wiem, że jest to nieco rozciągnięty przykład, ale jest to opcja – Eineki

2

bym instancja obiektu klon pomocą konstruktora obiektu zostać sklonowany:

var copy = {}; 

będzie

var copy = new obj.constructor(); 

Jest to szybka odpowiedź i nie zastanawiałem się nad wadami takiego rozwiązania (myślę o konstruktorze ciężkim), ale na pierwszy rzut oka powinno działać (nie wspomniałbym (lub nie polecę) ezoterycznego metody jako __proto__).

Aktualizacja:

trzeba uciekać się do object.create, aby rozwiązać ten problem.

var copy = {}; 

będzie

var copy = Object.create(obj.constructor.prototype); 

W ten sposób oryginalny konstruktor nie jest powołany do tworzenia obiektu (myślę o konstruktorze, dokłada się długie rozmowy ajax do pobierania danych z serwera) jako przedmiot.stworzenie odpowiada

Object.create = function (proto) { 
    function F() {} 
    F.prototype = proto; 
    return new F(); 
}; 

I można użyć tego kodu, jeśli javascript silnik używasz nie obsługuje tej funkcji (został dodany do ECMAScript 5 specyfikacje)

+0

Głównym problemem jest to, że 'obj.constructor' może nie odwoływać się do obiektu, z którego został zbudowany (np. Używając' beget' Crockforda, skopiowanego powyżej). Możesz być tylko pewien, czy skonstruowałeś obiekt w pierwszej kolejności, a w takim przypadku znasz konstruktora, więc dlaczego nie odnosić się do niego bezpośrednio? – RobG

+0

Aha, a prototyp konstruktora może być teraz innym obiektem, więc 'obj [[prototyp]]! == copy [[Prototype]]' nawet jeśli pochodzą od tego samego konstruktora. – RobG

+0

@RobG Nie rozumiem, chcę sklonować obiekt, utworzyć pusty z żądanym prototypem i skopiować oryginalny prototyp w celu utrzymania łańcucha prototypów. Istnieje metoda zmiany obiektu [[Prototype]] (myślę, że jest to wewnętrzna podpora do odczytu), aby różniła się od obiektu obj.constructor.prototype? Chciałbym (naprawdę) wiedzieć o takiej szansie – Eineki

4

Twoja funkcja można uprościć do:

function cloneObject(obj) 
{ 
    obj = obj && obj instanceof Object ? obj : ''; 

    // Handle Date (return new Date object with old value) 
    if (obj instanceof Date) { 
    return new Date(obj); 
    } 

    // Handle Array (return a full slice of the array) 
    if (obj instanceof Array) { 
    return obj.slice(); 
    } 

    // Handle Object 
    if (obj instanceof Object) { 
    var copy = new obj.constructor(); 
    for (var attr in obj) { 
     if (obj.hasOwnProperty(attr)){ 
      if (obj[attr] instanceof Object){ 
       copy[attr] = cloneObject(obj[attr]); 
      } else { 
       copy[attr] = obj[attr]; 
      } 
     } 
    } 
    return copy; 
    } 

    throw new Error("Unable to copy obj! Its type isn't supported."); 
} 

Oto working jsfiddle

Powiązane problemy