Después de este juego de palabras tan ingenioso que lleva el título de este Post, explicaré a qué se debe. Si avanzamos en Javascript orientado a objetos tarde o temprano aprenderemos una lección que difícilmente podremos olvidar. Y es la funcionalidad del operador de asignación, o lo que es lo mismo, el símbolo de “=”.
El igual es un arma de doble filo y que en muchos otros lenguajes se utiliza para guardar valores de unas variables a otras. Pues bien, en Javascript esto es mentira y seguramente no nos habremos dado cuenta si solo utilizamos variables de tipo número o String. Para estos dos tipos de variable, se crea una copia independiente al asignarla dentro de otra variable, pero para el resto de tipos no es así, sino que se crea una referencia. Ésto es que el nombre de la variable es distinto, pero si modificamos el valor utilizando cualquiera de esos nombres de variable que hemos asignado, se modificarán de forma bilateral.
Vamos a poner el ejemplo de un array. Con este código creamos un array y lo igualaremos a otra variable, para después observar los resultados que obtenemos al modificarlas.
var foo = ["Banana", "Orange", "Apple","Kiwi", "Mango"]; var bar = foo; /*Modificamos el contenido del array en la variable bar (con los colores de la palabra).*/ bar[0]="Yellow"; bar[2]="Green"; /*Modificamos el contenido del array en la variable foo (con números que indican el número de letras de la palabra anterior).*/ foo[1]=6; foo[3]=4; //Y ahora observemos el resultado de las dos variables. console.log('Array foo: '+foo); console.log('Array bar: '+bar);
Y obtendremos el siguiente resultado:
Array foo: Yellow,6,Green,4,Mango Array bar: Yellow,6,Green,4,Mango
Como podremos observar, las dos variables arrojan el mismo resultado, y los cambios que hemos realizado afectan tanto a una como a otra. Este hecho supone un problema a la hora de manipular variables, cuando solemos creer que son simples copias, caemos en un gran error y realmente estamos modificando el estado de otra variable.
Para solucionar este problema con arrays podemos usar la función “slice()“. Slice permite copiar elementos o un conjunto de elementos de un array e igualarlos a una variable. De esta manera no caeremos en errores y no se creará la referencia entre variables.
var foo = ["Banana", "Orange", "Apple","Kiwi", "Mango"]; var bar = foo.slice(); bar[0]="Yellow"; bar[2]="Green"; foo[1]=6; foo[3]=4; //Y ahora observemos de nuevo el resultado de las dos variables. console.log('Array foo: '+foo); console.log('Array bar: '+bar);
Ahora obtendremos los siguientes resultados, que evidentemente es más útil a la hora de desarrollar nuestras copias de variables.
Array foo: Banana,6,Apple,4,Mango Array bar: Yellow,Orange,Green,Kiwi,Mango
Esto no acaba aquí, por ahora hemos hablado de los tipos número, string y arrays que son los tipos más utilizados en javascript. Pero ahora supongamos que creamos un objeto personalizado. O aún peor, imaginemos que estamos utilizando un Framework javascript. Si no sabemos realmente como se ha integrado, lo mejor es hacer una prueba y aún más importante buscar en la documentación si existe la función para crear copias de estas variables sin tener que utilizar el signo “=”.
Y esto es porque la única manera de no crear referencias entre objetos es crear una función copia definida específicamente para estos casos.
A continuación exponemos un ejemplo de un objeto espécifico al que le añadiremos una función copy() para evitar este problema. Existen varias soluciones, así que ya puestos, presento mi solución propia y que utilizo en un framework de robótica que estoy traduciendo de Matlab. Enlace aquí.
function Link(theta,d,a,alpha,sigma){ this.theta = 0; this.d = 0; this.a = 0; this.alpha = 0; this.sigma = 0; } Link.prototype.copy = function (copyLink){ var newLink = new Link(); if (copyLink instanceof Link){ for(var key in copyLink) { if (typeof(copyLink[key])!='function'){ if (copyLink[key] instanceof Array){ eval('this.'+key+ ' = ['+copyLink[key]+']'); }else{ eval('this.'+key+ ' = '+copyLink[key]); } } } } } L1 = new Link(1,2,3,4,5); L2 = L1; L3 = new Link(); L3.copy(L1); /*Realizamos modificaciones varias*/ L1.theta=10; L2.d='dos'; L3.a=[1,2]; /*Mostramos los resultados por consola*/ console.log('Link1: '+L1); console.log('Link2: '+L2); console.log('Link3: '+L3);
Esta manera aunque puede parecer un poco rebuscada, hace un recorrido por todos los parámetros del objeto y los iguala. Hay que hacer un filtro para las funciones, ya que en javascript, las funciones también se declaran como parámetros. Es una locura, pero este lenguaje es así.
Este método funciona cuando los parámetros son números o strings pero si son arrays u otro tipo de objetos es conveniente aplicarles un filtro para copiarlos de la misma manera que hemos explicado.
Por otra parte, hay que tener mucho cuidado con el parámetro “name”. Javascript parece ser que tiene reservado este nombre de parámetro en objetos para manipularlo de forma diferente y que explico en el siguiente post, porque es demencial las cosas que pueden llegar a ocurrir.
Otros frameworks como JQuery utilizan la función extend para crear estas copias sin problemas.
Y otro truco más es convertir el objeto en una cadena JSON por medio de la función “stringify()“. Como las cadenas no crean referencia, se pueden manipular de forma sencilla y volviendo a crear el objeto.
Aquí copio un ejemplo útil que he encontrado.
var foo = { "name" : "Muzzy", "genre" : "Animation", "year" : "1986", "characters" : [ "Muzzy", "King", "Queen", "Pricess Sylvia" ] }; var bar = JSON.parse( JSON.stringify( foo ) ); bar[ 'name' ] = "Muzzy in Gondoland"; console.log( foo[ 'name' ] ); // Muzzy console.log( bar[ 'name' ] ); // Muzzy in Gondoland
Dejo aquí este enlace de otro interesante blog para más aclaraciones. No desesperéis, siempre hay alguna solución.
One comment