JavaScript-VBScript: Paso de argumentos a funciones
Las funciones (y procedimientos en VBScript) nos permiten pasar argumentos o parámetros para manipularlos en su interior. Son conocidas dos estrategias como el paso por valor y el paso por referencia. JavaScript sólo permite el paso por valor y VBScript permite ambos. Para ver el comportamiento de ambos lenguajes, realizamos algunos ejemplos.
Paso de argumentos por valor a una función de JavaScript
Para las funciones de JavaScript los argumentos declarados como número y booleanos seran considerados en paso por valor. Así lo que estamos pasando es realmente una copia de esa variable. Cualquier cambio de esa variable en el interior de la función no afecta al original. En este ejemplo pasamos un número y un booleano y los modificamos dentro:
<script> function modificaNumBool(unNumero, unBooleano) { unNumero = 9999; unBooleano = true; } var miNumero = 1; var miBooleano = false; </script> Paso de números y booleanos por valor: <input type="button" value="modifica(unNumero, unBooleano)" onclick = "modificaNumBool(miNumero, miBooleano); alert('miNumero = ' + miNumero + ', miBooleano =' + miBooleano)" />
Ejemplo:
Paso de números por valor:Se observa que tenemos una función que recibe unos argumentos y luego los modifica dentro de ella. Declaramos el número 1 y el booleano false y con la ejecución del botón llamamos a esa función para cambiar esas variables a 9999 y true, pero al final volvemos a obtener 1 y false. Sucede que unNumero
y unBooleano
son variables que JavaScript toma por defecto en paso por valor y por tanto sólo tiene un alcance interno en la función. Los cambios hechos en ella no afectan al original desde donde se copió.
El paso por valor ocasiona que Javascript deba crear memoria adicional para albergar las copias. Esto no es importante si lo que ocupan los valores no es significativo, pero en otro caso habrá que tenerlo en cuenta.
Paso de argumentos por ¿referencia? a una función de JavaScript
Por defecto, Javascript pasará los objetos, arrays y funciones por referencia. Pero esto no es del todo cierto y es por lo que le hemos puesto unos interrogantes a este título, como comprobaremos al finalizar este apartado. En este primer ejemplo pasamos un Object de Javascript:
<script> function modificaObjeto(unObjeto) { unObjeto.propiedad = "Valor MODIFICADO"; } var miObjeto = new Object(); miObjeto.propiedad = "Valor original"; </script> Paso de objetos por referencia: <input type="button" value="SI modifica mis datos" onclick = "modificaObjeto(miObjeto); alert('miObjeto.propiedad=' + miObjeto.propiedad)" />
Ejemplo:
Paso de objetos por referencia:La función recibe un objeto con una propiedad y modifica ésta en su interior. Luego con el alert
obtenemos la propiedad modificada en el objeto original. Sin embargo otra cosa es que modifiquemos la naturaleza del propio objeto, por ejemplo declarando dentro de la función un nuevo objeto en ese mismo argumento:
<script> function noModificaObjeto(unObjeto) { unObjeto = new Object(); unObjeto.propiedad = "Valor MODIFICADO"; } var miObjetoB = new Object(); miObjetoB.propiedad = "Valor original"; </script> Paso de objetos por referencia: <input type="button" value="NO modifica mis datos" onclick = "noModificaObjeto(miObjetoB); alert('miObjetoB.propiedad=' + miObjetoB.propiedad)" />
Ejemplo:
Paso de objetos por referencia:Ahora el objeto se vuelve a declarar en el interior de la función, con lo que pierde la referencia al objeto original, siendo ahora una nueva variable con alcance limitado a ese interior. Así el mensaje nos devuelve a la propiedad del objeto original, no la del modificado. Esto también pasa con un Array:
<script> function modificaArray(unArray){ unArray.push(unArray.length+1); } function noModificaArray(unArray) { unArray = new Array("a", "b", "c"); } var miArray = new Array(1, 2, 3); </script> Paso de arrays por referencia: <input type="button" value="NO modifica mi array" onclick = "noModificaArray(miArray); alert(miArray)" /> <input type="button" value="SI modifica mi array" onclick = "modificaArray(miArray); alert(miArray)" />
Ejemplo:
Paso de arrays por referencia:¿Entonces los objetos se pasan por referencia o no?. Diríamos que si y no. Esto se explica entendiendo que JavaScript pasa todos los argumentos POR VALOR. En el caso del paso de objetos lo que hace es copiar el puntero de la referencia al objeto, o dicho de otra forma, hace una copia del valor de ese puntero, que no es otra cosa que una dirección de memoria. Pero es una copia de la referencia, no la referencia original, por lo que cuando asignamos a ese puntero un nuevo objeto dentro de la función, entonces no se modifica el objeto original sino esa copia de puntero. Otra cosa es que, mientras no se modifique ese puntero, Javascript permite acceder a las propiedades del objeto que apunta y, por tanto, modificarlas.
Es una cuestión de terminología, pero se puede decir que Javascript no pasa por referencia en ningún caso. Sólo por valor para números y booleanos y con copia de la referencia para los objetos. A esta estrategia a veces también se le llama call by object o call by object-sharing (llamada por objeto o por objeto compartido), cuyo comportamiento según vimos es el siguiente:
- Permite acceder al objeto original, del que tenemos una copia de la referencia, para modificar sus propiedades y métodos. Este es un comportamiento "equivalente" al paso por referencia.
- Las nuevas asignaciones hechas a la referencia copiada no modifican el objeto original sino la copia, comportándose en este caso como un paso por valor.
Esta confusión es muy general hasta el punto de que el MSDN de JScript (la versión de JavaScript para el Internet Explorer), define que pueden pasarse argumentos por referencia, como puede verse en passing data by value vs. by reference con el siguiente texto:
JScript copies, passes, and compares objects, arrays, and functions by reference. This process essentially creates a reference to the original item and uses the reference as if it were a copy. Changes to the original change both the original and the copy (and vice versa). There is really only one entity; the copy is just another reference to the data.
que podríamos traducir como
JScript copia, pasa y compara objetos, arrays y funciones por referencia. Este proceso esencialmente crea una referencia al objeto original y usa la referencia como si fuera un copia. Los cambios en el original se reflejan en la copia y viceversa. Hay sólo una entidad; la copia es justo otra referencia a los datos.
En definitiva, sea cual sea la terminología usada para definir ese peculiar paso por referencia, no deja de ser un paso por valor. No se debería usar el término por referencia para este caso, pues da lugar a confusión para quiénes están esperando un verdadero paso por referencia, como por ejemplo el que hace VBScript que explicamos en el siguiente apartado.
Paso de argumentos por referencia en funciones de VBScript
VBScript sí que nos permite además del paso por valor un verdadero paso por referencia. Expongamos este código que crea un objeto (una clase en la denominación de VBScript):
<script type="text/vbscript"> //<![CDATA[ 'Declara una clase Class vbObjeto Public propiedad End Class 'Procedimiento para modificar una propiedad Sub modificaProp(ByRef unObjetoVb) unObjetoVb.propiedad = "Nuevo valor MODIFICADO" End Sub 'Instanciamos un nuevo objeto de aquella clase Dim miVbObjeto Set miVbObjeto = New vbObjeto miVbObjeto.propiedad = "Valor original" //]]> </script> <b>Paso de objetos ... <input type="button" value="botón 1" onclick = "vbscript: Msgbox('miVbObjeto.propiedad=' & miVbObjeto.propiedad)" /> ....Si después pulsamos.... <input type="button" value="botón 2" onclick = "vbscript: Call modificaProp(miVbObjeto) : Msgbox('miVbObjeto.propiedad=' & miVbObjeto.propiedad)" />
Function
devuelven valores mientras que los procedimientos Sub
no lo hacen. Es una distinción que no afecta al comportamiento de este ejemplo, pero usamos el procedimiento, pues igual que con los ejemplos de JavaScript, no necesitamos devolver valores. Por otro lado el &
dentro de los Msgbox()
(como alert() en JavaScript
), concatena cadenas y nada tiene que ver con el operador lógico de conjunción que es and
.Se declara la clase con Class vbObjeto
, definiendo una propiedad
. Luego tenemos el procedimiento que va a modificar esa propiedad de ese objeto, el cual se pasa como argumento especificando ByRef
explícitamente. VBScript permite pasar por valor con ByVal
o por referencia, el cual es el predeterminado si no se indica nada, aunque aquí lo ponemos para que quede claro. Así la propiedad modifica la original del objeto que se pasó por referencia. Con los botones del ejemplo, pulsando el primero antes que el segundo, podrá comprobarlo.
Ejemplo:
Paso de objetos por referencia:Con este tenemos el valor original de miVbObjeto.propiedad
que se puso al crear el nuevo objeto.
Si después pulsamos este
Para observar que el argumento se está pasando por verdadera referencia, hemos de hacer un ejemplo donde una nueva asignación del argumento dentro de la función cambie el tipo de esa variable en el exterior de la función. Esto es prueba evidente de que estamos accediendo por verdadera referencia, cosa que no hacía JavaScript. Veámos el siguiente ejemplo:
<script type="text/vbscript"> //<![CDATA[ 'Declaramos una clase Class vbObjeto2 Public propiedad End Class 'Este procedimiento crea otra vez el objeto con un 'nuevo valor para su propiedad Sub modificaRef(ByRef unObjetoVb) Set unObjetoVb = new vbObjeto2 unObjetoVb.propiedad = "Valor MODIFICADO" End Sub 'Este procedimiento asigna un tipo diferente al argumento Sub modificaRef2(ByRef unObjetoVb) Set unObjetoVb = Nothing unObjetoVb = 123 End Sub 'Instanciamos un nuevo objeto Dim miVbObjeto2 Set miVbObjeto2 = New vbObjeto2 miVbObjeto2.propiedad = "Valor original" //]]> </script> ... <input type="button" value="botón 1" onclick = "vbscript: Msgbox('TypeName(miVbObjeto2)=' & TypeName(miVbObjeto2))" /> ... <input type="button" value="botón 2" onclick = "vbscript: Call modificaRef(miVbObjeto2): Msgbox('miVbObjeto2.propiedad=' & miVbObjeto2.propiedad)" /> ... <input type="button" value="botón 3" onclick = "vbscript: Call modificaRef2(miVbObjeto2): Msgbox('miVbObjeto2=' & miVbObjeto2)" />. ...
Ejemplo:
Paso de objetos por referencia real:Este nos dice el TypeName
, el tipo de la variable miVbObjeto2
que inicialmente será un tipo vbObjeto2
.
Si ahora modificamos el argumento pasado por referencia al objeto con Set unObjetoVb = new vbObjeto2
para crear otra vez el objeto y modificar su propiedad, estenos dará el nuevo objeto re-instanciado con un nuevo valor para su propiedad. Ahora el primer botón nos dirá que miVbObjeto2
es un tipo vbObjeto2
(el mismo, pero creado otra vez).
Si ahora modificamos la referencia al objeto con Set unObjetoVb = Nothing
de tal forma que el objeto no contenga nada y luego asignamos a esa referencia un número entero con unObjetoVb = 123
, entonces hemos modificado la referencia original, incluso a otro tipo, tal como nos dará esteAhora el primer botón nos dirá que miVbObjeto2
es un tipo Integer
.
Hay que tener cuidado con la llamada a modificaRef
especialmente, que ha de pasarse sin paréntesis como modificaRef miVbObjeto2
o bien usando el llamador Call modificaRef(miVbObjeto2)
, pues aunque el procedimiento se explicitó con ByRef
si en la llamada se usan paréntesis sin Call
en este caso se pasará por valor. Como esta hay otras salvedades donde VBScript ignora la declaración ByRef
y pasa el argumento por valor, lo cual se explica en esta página del MSDN de VBScript.
En definitiva, JavaScript sólo permite pasos por valor mientras que VBScript si permite además el paso por referencia. La especificación oficial de Javascript se puede encontrar en la página ECMA, a partir del cual también encontramos un artículo muy completo em ECMA-262-3 in detail. Chapter 8. Evaluation strategy escrito por Dimitry A.Soshnikov. En esta página de Wikipedia también hay información al respecto sobre el paso de argumentos.