Contenido

Interesante curiosidad javascript

10 jun

+ 35

Esta mañana he encontrado un artículo, que esta noche publicaré, en el que me he encontrado una interesante curiosidad javascript, os la dejo y a ver si alguien lo encuentra :D


var divs = document.getElementsByTagName("div");
for (var i=0; i < divs.length; i++) {
       var div = document.createElement("div");
       document.body.appendChild(div);
}

Primero, y sin probar el código intenta comprender que es lo que hace el script, una vez visualizado y comprobado que no haya errores, pruebalo, ya sea en Firebug, o en una página aposta. ¿A ver si alguien me sabe decir por que ocurre esto? Esta noche, o mañana por la mañana pondré la solución y el enlace donde lo ví, pero seguro que lo sacáis antes :D …. tiempo!!

Intenta no leer los comentarios y contestar lo que has pensado.

Actualización

Bueno, los comentaristas están mucho más puestos que yo en Javascript, y nos han deleitado con comentarios explicativos imposibles de mejorar, así que si quieres saber algo más sobre los HTMLCollection, NodeList y demás, lee los comentarios. El código lo he sacado de un artículo de Nicholas C. Zakas sobre optimización Javascript y me ha parecido muy curioso :D

  • Supongo que como en cada iteracción comprueba el divs.length y se añade un div, añadirá divs hasta el infinito :D

  • var divs = document.getElementsByTagName("div");
    var max = divs.length;
    for (var i=0; i < max; i++) {
           var div = document.createElement("div");
           document.body.appendChild(div);
    }
  • @ZiTAL: Hola ZiTAL, CREO que te equivocas, esa variable se haya UNA vez, tendrías razón (CREO) si el código fuese

    
    
    for (var i=0; i < document.getElementsByTagName("div").length; i++) {
           var div = document.createElement("div");
           document.body.appendChild(div);
    }
    
    

    Yo creo que duplicará el número de divs…

  • Un voto a favor de la solución de ZiTAL
    Yo creo que hará iteraciones infinitas SI cuando empieza hay al menos un div.

  • Lo que devuelve getElementsByTagName es un array interno del navegador que se actualiza con cada CreateElement del mismo elemento. jay, si crees que duplicará el número de elementos, pruebalo en tu navegador, pero guardalo todo antes… ;-)

  • Pues ahora mismo no se por qué será, luego le echaré un vistazo más detenidamente. Lo que si he observado es que divs.length me devuelve 0, sin embargo si miras el DOM con firebug…

  • Creo que seria un bucle infinito si dentro del for la condición fuera:

    i < document.getElementsByTagName('div").length;

    pero como no es asi la primera llamada a getElementsByTagName devuelve una nueva lista de los nodos de ese tipo que no se actualiza con cada iteración.

    Asi que solo duplica el numero de divs en la pagina

  • Yo coincido con @alsanan, creo que getElementsByTagName devuelve una referencia a una hash interna que se mantiene creciendo si añades nuevos divs.
    Es curioso la verdad

  • La curiosidad es la que ha puesto de relieve ZiTAL, y lamento discrepar en lo de que es “una interesante curiosidad javascript”. Es una desagradable característica del DOM: los NodeList ¡están demasiado vivos!. Es una “enfermedad” que afecta a cualquier implementación del DOM (las de Java por ejemplo) .

    Veamos un poco de la spec

    getElementsByTagName

    Returns a NodeList of all the Elements with a given tag name in the order in which they are encountered in a preorder traversal of the Document tree.

    Interface NodeList

    The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live.

    1.1.1. The DOM Structure Model

    NodeList and NamedNodeMap objects. For example, if a DOM user gets a NodeList object containing the children of an Element, then subsequently adds more children to that element (or removes children, or modifies them), those changes are automatically reflected in the NodeList, without further action on the user’s part. Likewise, changes to a Node in the tree are reflected in all references to that Node in NodeList and NamedNodeMap objects.

    Por tanto una implementación del DOM esconde mucho detrás de una invocación de Node::childNodes o Node::getElementsByTagName. Cada vez que se invoca uno de esos métodos debe quedar registrado un Observer al que tienen que dar cuenta todas las operaciones que alteren ciertas regiones del documento. Esto no es nada barato en cuestión de rendimiento. Tener unas cuantas referencias a NodeList es como tener un puñado de zombies a las puertas de tu casa.

    No es por casualidad que en el draft de la spec de los selectores DOM se tenga otro enfoque

    ….
    The NodeList object returned by the querySelectorAll() method must be static, not live. Subsequent changes to the structure of the underlying document must not be reflected in the NodeList object. This means that the object will instead contain a list of matching Element nodes that were in the document at the time the list was created.

    Simplemente imaginar que un observador automáticamente registrado tenga que comprobar el encaje del selector, que caprichosamente hayamos establecido, en cada modificación de ciertas regiones del documento (tal vez en su totalidad) me produce escalofrios. Es obvio que como es inviable desde el punto de vista de la eficiencia en este caso los del comite correspondiente han asegurado que los NodeList proporcionados por el futuro querySelectorAll esten bien tranquilitos.

    Por otra parte, la nomenclatura del API es totalmente opaca en este asunto: los nombres de los métodos invitan a pensar en un snapshot, una simple colección de nodos, no un objeto complejo que registra observadores complejos capaces de mutar de forma compleja.

    Resumiendo, side-effects brutales y futura falta de ortogonalidad. El asunto es desagradable.

  • Desde mi punto de vista, y sin saber si es cierto o no, la respuesta es BUCLE INFINITO, y la razón es: el método getElementsByTagName devuelve recursivamente todos los nodos de tipo div (en este caso) de nodo padre, que es document, bien, cuando se crea un nuevo elemento con document.createElement y se añade con appendChild automáticamente se modifica el valor de length, y eso es por que se añade por referencia, es decir, es como si hicieramos en PHP algo como:

    
    <?php
    function createElement(&$key) {
    	$key++;
    	
    	return 'a';
    }
    
    $key = 5;
    for($i = 0; $i <
    

    Es decir, le pasamos el contador por referencia y es lo que hace que se genere un bucle infinito.

  • Lo má sencillo es:

    
    var divs = document.getElementsByTagName("div").length;
    
    	for (var i=0; i < divs; i++) {
           var div = document.createElement("div");
           document.body.appendChild(div);
    }
    
    

    Así ya guardamos el dato en caché y no le forzamos a que vuevla a consultar todos los divs cada vez que repasa el bucle.

  • Por cierto, es muy saludable pasar los NodeList por Prototype::$A o MooTools::$A. Con estas simples funciones obtenemos lo que normalmente queremos y dejamos que los hambrientos zombies nodelistianos sean recogidos por el camión de la basura :D

  • hola amigos, supongo que mas que una función cualquiera es un método que regresa una referencia que se modifica internamente lo que no se es si eso es bueno o malo
    saludos
    :]

  • @joseanpg: Aguafiestas :D
    @joseanpg: Sabelotodo :D (Desde el cariño :P)

    Ha sido fallo mio pensar que tendría hasta la noche para que se descubra el pastel, iluso ¬¬.

    El problema es como @ZiTAL comenta en su comentario. Esto es debido a que en este caso la variable divs no es únicamente un array sinó que es un HTMLCollection, un tipo de array dinámico que se actualiza automáticamente.

    Como dice @joseanpg, se trata de un problema del DOM y que conociendolo podemos solucionarlo fácilmente.

    
    var divs = document.getElementsByTagName("div");
    for (var x in divs) {
          var div = document.createElement("div");
          document.body.appendChild(div);
    }
    
  • Ciertamente, lo he probado y el navegador te avisa de bucle infinito. Lo que dije antes de que divs.length me devolvía 0, fallo mio, ejecutaba el script en el header antes de crear los divs. Eso me llevó a pensar por qué firebug me daba correcta la longitud del array: al crear los elementos se actualiza ‘divs’, ya que se hacen cambios en el DOM.

    @joseanpg realmente es una desagradable propiedad DOM, para personas como yo, que no sabía ni siquiera por qué divs.length me daba 0, y la verdad es que me ha costando encotrar el fallo xDDD. Aún así, crreo que para personas que saben lo que hacen y por qué ese bucle es infinito, facilita la tarea si, por ejemplo, estas creando divs. Si al crear un elemento te actualiza directamente el array, ahorras una línea de código.
    Si necesitas tener el número de divs como elemento de control, lo más sencillo es guardar la longitud en una variable, como bien dice Fernando.

    Realmente, para mí, ha sido una curiosa experiencia.

  • @14 realmente crees que es un problema?
    Como bien dices se puede solucionar facilmente, y puede tener ventajas. Perdón por repetirme, pero no me parece un problema…

  • Estoy de acuerdo con ZiTAL, el script lo que realiza es crear un número infinito de divs pero teniendo en cuenta que exista POR LO MENOS un div creado en el documento, si no existe, no hace nada.

  • Ten en cuenta ~sh que siempre habrá quien defienda la gestión manual de memoria, los punteros, etc. Lo que es indiscutible es que trabajando uno con esas cosas puede meter la pata más fácilmente, por mucho que domine el tema.

    Esto es lo mismo, un atractor de efectos laterales.

  • @joseanpg: Te comprendo. Casi seguro que la mayoría de la gente después no se acuerda que es un objeto dinámico, y recurren a controlar el bucle con él. Me pregunto cómo se habrá dado cuenta Andrés.

    ….
    madre mia, después de probar con el nombre y el número, acabo de aprender a quotear :D

  • Yo lo solucionaría así:

    var divs = document.getElementsByTagName("div");
    for (var i=0; i < (divs.length - i) ; i++) {
           var div = document.createElement("div");
           document.body.appendChild(div);
    }
    
  • concuerdo con jay, duplicara los div

  • @Francisco: No hijo no, mira arriba… T_T

    Por estas cosas, y el cross-browsing, odio javascript, con lo bonito que es el php…

  • Bueno sin leer los comentarios y tratando de imaginar lo que hace es duplicar todos los divs, aunque me imagino si existen divs anidados no los anidara solo pondra un div bajo el otro… eso es tooo lo que entendi

  • Es mas curioso el siguiente experimento:
    Primero extender el elemento Bool

    Boolean.prototype.negar = function() { return !this; }

    Y luego hacer pruebas como:
    true.negar() y false.negar()

    Sorprendente, pero JS tambien se equivoca xD

  • https://developer.mozilla.org/es/DOM/document.getElementsByTagName

    “elements es una lista ‘viva’ (NodeList) de los elementos encontrados en el orden en que han aparecido en el árbol. ”

    guardar el length en una variable para el “for” y listo, en si esto se debería hacer para todos los length o count para evitar el recalculo y demora del mismo

  • ragamo, mmm, “this” es un objeto, sino fijate de hacer lo siguiente:

    Boolean.prototype.negar = function() { return typeof(this); }
    alert("false " + typeof(false) + " " + false.negar());
    alert("true " + typeof(true) + " " + true.negar());
    

    y vas a ver el resultado.

  • El caso expuesto es equivalente a:

    
     for (var i=0; i < document.getElementsByTagName("div").length; i++) {
        var  div = document.createElement("div"); 
           document.body.appendChild(div);
      }

    Por lo que siempre el número de nodos hijos del nodo BODY (o sea DIVs) se incrementará progresivamente.
    Una posible solución sería:

    var divs = document.getElementsByTagName("div");
    var len = divs.length;
     for (var i=0; i < len; i++) {
        var  div = document.createElement("div"); 
           document.body.appendChild(div);
      }
  • @ragamo: ragamo, analiza este código y vas a ver que JS no se equivoca, sino que es otro tipo de dato el “this“.

    Boolean.prototype.tipo = function() { return typeof(this); }
    alert("false " + typeof(false) + " " + false.tipo());
    alert("true " + typeof(true) + " " + true.tipo());
  • si este tipo de array tuviera un evento que se disparara cada vez que su longitud cambia, esto mas que un problema seria una ventaja, imaginen algo como el live() de jQuery pero nativo del navegador

  • Igual es una chorrada lo que digo, pero el script no funciona igual si está en el HEAD (no va) que si está al final de la página (bucle infinito), lo digo por aqéllos a los que divs.length les devuelve 0

  • @daTO es lógico, en el HEAD todavía no esta el body construido, en cambio en el HEAD dentro de window.onload = function(){}; si que lo haría.

  • Ya, es la manía de poner todos los script en el HEAD, tal y como nos enseñaron años ha… y ya voy oyendo por todas partes que mejor al final,

    Y exacto, mejor con window.onload = function(){}

    interesante script, por eso …

  • La ventaja que tiene añadir los scripts al final es que el usuario no tiene que esperar a que el script cargue para disfrutar del resto del contenido. Sin embargo si para visualizar la página es necesario javascript, lo mejor es en la cabecera y con un DOMContentLoaded en vez de window.onload.

  • @joseanpg: Inmejorable explicación. Siempre es un gusto leer tus comentarios :)

  • @andres descalzo: queda comprobado que el que se equivoca soy yo xD
    se agradece

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.