Strona główna HTML i javascript C++ Hardware HTML i javascript Java Software SQL

JavaScript – kopiowanie obiektów i związane z tym problemy.

Dodano: 2012-08-17

JavaScript jest językiem który wykonuje wiele operacji domyślnie w czasie wykonywania kodu, by umożliwić użytkownikowi łatwość pisania skryptów. Niestety te ułatwienia są często problemem. Przykładem jest kopiowanie obiektów przez ich domyślne przypisanie za pomocą operatora „=”. W JavaScript jest tworzony wskaźnik do obiektu, co sprawia, że modyfikując nowo utworzony obiekt modyfikujemy jego pierwowzór. Przykład poniżej:

<html>
<head>
<script type="text/javascript">

function cl(){
	this.setA = function(x){
		this.a = x;
	}

	this.getA = function(){
		return this.a;
	}
	
	var a;
}

</script>
</head>

<body>
<div id="TEMP">
</div>
</body>

<script type="text/javascript">
	document.getElementById("TEMP").innerHTML += "Start...<br>";
	var o1 = new cl();
	o1.setA(3);

	// czy aby na pewno się kopiuje obiekt o1 ????
	// niestety nie o2 to tylko wskaźnik to o1
	var o2 = o1;

	// innerHTML - zastosowano tylko w celach edukacyjnych
	// w końcowej fazie projektu javascript nie używaj go ponieważ nie jest ujęty w specyfikacji js
	document.getElementById("TEMP").innerHTML += "o1: " + o1.getA() + "<br>";
	document.getElementById("TEMP").innerHTML += "o2: " + o2.getA() + "<br>";
	o2.setA(5);
	document.getElementById("TEMP").innerHTML += "o1: " + o1.getA() + "<br>";
	document.getElementById("TEMP").innerHTML += "o2: " + o2.getA() + "<br>";

	document.getElementById("TEMP").innerHTML += "Koniec<br>";

</script>
</html>

Wyniki:

Start...
o1: 3
o2: 3
o1: 5
o2: 5
Koniec

Modyfikując obiekt o2, modyfikujemy tak naprawdę obiekt o1, ponieważ o2 jest wskaźnikiem do o1. Takie „kopiowanie” obiektów może przyczynić się do dużych problemów z działaniem skryptu.

Chcąc mieć pewność, że obiekt zostanie skopiowany należy stworzyć funkcję np.: clone(), która zadba o to by zwracany obiekt był kopią, a nie wskaźnikiem do pierwowzoru. W internecie istnieje wiele przykładów funkcji, i ich skomplikowanie zależy czy mają kopiować oprócz obiektów np. tablice. Poniżej przykład edukacyjny funkcji clone() kopiującej obiekty z przykładu powyżej:

function clone(obj){
	var tt = {};
	for (var i in obj)
		tt[i] = obj[i];
	return tt;
}

a teraz cały kod z prawidłowym kopiowaniem:

<html>
<head>
<script type="text/javascript">

function cl(){
	this.setA = function(x){
		this.a = x;
	}

	this.getA = function(){
		return this.a;
	}
	
	var a;
}

function clone(obj){
	var tt = {};
	for (var i in obj)
		tt[i] = obj[i];
	return tt;
}
</script>
</head>

<body>
<div id="TEMP">
</div>
</body>

<script type="text/javascript">
	document.getElementById("TEMP").innerHTML += "Start...<br>";
	var o1 = new cl();
	o1.setA(3);

	// teraz kopia ;)
	var o2 = clone(o1);

	// innerHTML - zastosowano tylko w celach edukacyjnych
	// w końcowej fazie projektu javascript nie używaj go ponieważ nie jest ujęty w specyfikacji js
	document.getElementById("TEMP").innerHTML += "o1: " + o1.getA() + "<br>";
	document.getElementById("TEMP").innerHTML += "o2: " + o2.getA() + "<br>";
	o2.setA(5);
	document.getElementById("TEMP").innerHTML += "o1: " + o1.getA() + "<br>";
	document.getElementById("TEMP").innerHTML += "o2: " + o2.getA() + "<br>";

	document.getElementById("TEMP").innerHTML += "Koniec<br>";

</script>
</html>

W niektórych złożonych przypadkach kopiowanie za pomocą użycia prostej funkcji nie będzie działało poprawnie. Najwięcej problemów stwarzają kopiowanie elementów rozbudowanych klas. Osobiście polecam tworzenie metody clone dla konkretnej klasy, dzięki czemu zapobiegamy jakimkolwiek niejasnością w czasie kopiowania elementu.

Na stronie Brian Huisman napisał ładną funkcję klonującą, która jako jedyna poradziła sobie ze skopiowaniem prawidłowym elementu mojej klasy.

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

Moja klasa, z którą miałem problem, posiadała w sobie oprócz kilku pól różnej maści ;) również tablicę n-elementową obiektów innej klasy. Potrzebowałem pobrać element z pozycji „i”, by móc odczytać zawarte w nim informację, niestety użycie clone() w postaci pierwszego przykładu i wielu innych, które znajdywałem w internecie nie wykonywały kopi elementu, a z zwróconego wyniku można było modyfikować nadal element tablicy (co ciekawe wszystkie metody clone działały prawidłowo na prostych klasach).

Ostatecznie zrezygnowałem z funkcji clone() "ogólnej" na rzecz napisania dedykowanej funkcji kopiującej dotyczącej mojej klasy i klas pochodnych, co sprawiło, że jestem pewny że zawsze uzyskam kopię elementu jeżeli tylko będę tego chciał.