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)" />
Nota VBScript: En VBScript las funciones 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

Sólo para Internet Explorer: Este ejemplo aparece activado sólo para Internet Explorer y otros navegadores basados en MSIE (si es que los hay). El resto aparecerá con los botones desactivados para no cursar el error pues no aceptarán las llamadas en VBScript.

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.