JavaScript: Pasar referencias al DOM en un objeto con Internet Explorer

Pasar referencias al DOM

Para entender el comportamiento del paso de argumentos por referencia o valor a funciones de JavaScript, es recomendable ver mi artículo paso de argumentos a funciones de JScript y VBScript.

JavaScript no soporta el paso por referencia, pues lo que se pasa es una copia de la referencia. Sin embargo con referencias a elementos HTML el comportamiento puede ser impredecible, al menos con Internet Explorer 8. Este artículo surge de la necesidad de crear objetos JavaScript que manipulan el DOM, es decir, que crean elementos en el documento. Esos objetos agregan con document.body.innerHTML nuevos elementos mediante la acción del usuario y después de finalizada la carga de la página. Si al objeto se le pasan argumentos con referencias a elementos antes de la modificación del DOM, al menos Internet Explorer pierde parte de esa referencia.

DOM: Se trata del Document Object Model o Modelo de Objetos del Documento, término que a veces usamos como el lugar donde se almacenan los elementos HTML del documento. Por ejemplo, con document.getElemenById("xxx") accedemos al DOM para encontrar un elemento con atributo id igual a "xxx".

Veámos este ejemplo con JavaScript donde se crea un objeto y se le pasa un argumento unLugar como una referencia a un elemento HTML que ya existe pues se declaró previamente al script y por tanto ya está en el DOM:

<div id="un-div" style="color: blue; border: blue solid 1px; margin-bottom: 1em;">
        Esto es el interior...
</div>
<script>
    function objeto(unLugar) {
        this.lugar = unLugar;
        this.haceAlgo = function haceAlgo(){
            var mensaje = "this.lugar = " + this.lugar + "<br />" +
            "typeof(this.lugar) = " + typeof(this.lugar) + "<br />" +
            "this.lugar.tagName = " + this.lugar.tagName + "<br />" + 
            "this.lugar.id = " + this.lugar.id + "<br />" +                                  
            "this.lugar.innerHTML = " + this.lugar.innerHTML + "<br />" +
            "this.lugar.childNodes.length = " + this.lugar.childNodes.length + "<br />" +                
            "document.getElementById(\"un-div\") = " + 
            document.getElementById('un-div').innerHTML + "<br />" +
            "document.getElementById(\"un-div\").childNodes.length = " + 
            document.getElementById('un-div').childNodes.length;   
            document.getElementById('resulta').innerHTML = mensaje;      
        }
    }
    var unLugar = document.getElementById("un-div");
    var miObjeto = new objeto(unLugar);
</script>
<input type="button" value="miObjeto.haceAlgo()"
onclick = "miObjeto.haceAlgo()" />
<div id="resulta"></div>

En el constructor el objeto recoge el argumento unLugar que es una referencia obtenido con var unLugar = document.getElementById("un-div"), que deberá apuntar al elemento siguiente con color y borde azul. Luego se crea el objeto con esa referencia HTML. Tenemos el método this.haceAlgo() para devolver una cadena con algunas propiedades y cosas que nos permitirán determinar el comportamiento de la referencia. Esta cadena será puesta con el botón en otro elemento div. El resultado se obtiene tras pulsar este botón (siempre después de cargar la página y antes de pulsar el segundo botón más abajo):

Ejemplo:

Esto es el interior HTML de un DIV con id="un-div".

Observamos que el argumento unLugar, almacenado en la propiedad this.lugar, es una referencia válida, pues dentro del método volvemos a buscar esa referencia con document.getElementById('un-div') obteniendo los mismos valores. Ahora bien, mientras no modifiquemos el DOM esto seguirá funcionando. Veámos el siguiente ejemplo donde ahora la referencia que se pasa al argumento apunta al elemento con borde y color rojo:

<div id="un-div2" style="color: red; border: red solid 1px; margin-bottom: 1em;">
    Esto es el interior...
</b>. 
</div>
<script type="text/javascript" defer="defer">
//<![CDATA[
    function objeto2(unLugar2) {
        this.lugar2 = unLugar2;
        this.haceAlgo = function haceAlgo(){
            document.body.innerHTML += "<div>Esto es <i>más</i>" +
                + "HTML <b>dinámico</b></div>";                
            var mensaje = "this.lugar2 = " + this.lugar2 + "<br />" +
            "typeof(this.lugar2) = " + typeof(this.lugar2) + "<br />" +
            "this.lugar2.tagName = " + this.lugar2.tagName + "<br />" +
            "this.lugar2.id = " + this.lugar2.id + "<br />" +
            "this.lugar2.innerHTML = " + this.lugar2.innerHTML + "<br />" +
            "this.lugar2.childNodes.length = " + this.lugar2.childNodes.length + "<br />" +   
            "document.getElementById(\"un-div2\") = " +
            document.getElementById('un-div2').innerHTML + "<br />" +
            "document.getElementById(\"un-div2\").childNodes.length = " +
            document.getElementById('un-div2').childNodes.length;            
            document.getElementById('resulta2').innerHTML = mensaje;
        }
    }
    var unLugar2 = document.getElementById("un-div2");
    var miObjeto2 = new objeto2(unLugar2);
//]]>    
</script>
<input type="button" value="miObjeto2.haceAlgo()"
onclick = "miObjeto2.haceAlgo()" />
<div id="resulta2"></div>

El ejemplo es igual que el anterior, pero dentro del método hacemos una modificación del DOM, es decir insertamos un nuevo elemento en el documento. Veámos que ocurre con la referencia del argumento que deberá apuntar a este elemento con borde rojo:

Ejemplo:

Esto es el interior HTML de otro DIV con id="un-div2".

Dado que el argumento se pasó por copia de la referencia, sigue conservando algunas cosas, como el tipo de elemento [object HTMLDivElement] e incluso sus atributos como el id. Pero esa referencia no está apuntando a ningún elemento del DOM pues no contiene nada en el interior, tal como se observa accediendo al DOM actual con document.getElementById('un-div2'), donde vemos que contiene el interior correcto.

Nota sobre childNodes: childNodes nos da una colección de los nodos hijo de un elemento, incluyendo los nodos texto, lo que da un total de 5 nodos.

Esto sucede con Internet Explorer 8 y aunque con Firefox 3.6, Opera 10.6 y Safari 4.0 no se evidencia, no se el alcance que puede tener. De todas formas prefiero seguir el principio de que con JavaScript no deben pasarse argumentos por referencia, o si se pasan por referencia tener en cuenta que es un copia de la referencia. Así cuando necesite pasar una referencia a un elemento HTML lo mejor es pasar un string con el identificador id de ese elemento. Luego dentro del objeto podemos actualizar la referencia simplemente con document.getElementById().