El equivalente en JavaScript puro de jQuery's $.ready() - cómo llamar a una función cuando la página/DOM está preparada para ello

Vale, puede que esta sea una pregunta tonta, aunque estoy seguro de que hay muchas otras personas que se hacen la misma pregunta de vez en cuando. Yo, sólo quiero asegurarme al 100% de cualquier manera. Con jQuery todos conocemos el maravilloso

$('document').ready(function(){});

Sin embargo, digamos que quiero ejecutar una función que está escrita en JavaScript estándar sin ninguna librería que la respalde, y que quiero lanzar una función tan pronto como la página esté lista para manejarla. ¿Cuál es la forma correcta de abordar esto?

Sé que puedo hacerlo:

window.onload="myFunction()";

...o puedo usar la etiqueta body:

<body onload="myFunction()">

...o incluso puedo probar en la parte inferior de la página después de todo, pero el final body o html etiqueta como:

<script type="text/javascript">
   myFunction();
</script>

¿Cuál es un método compatible con los navegadores (antiguos/nuevos) para emitir una o más funciones de manera similar a jQuery's $.ready()?

Solución

Lo más sencillo de hacer en ausencia de un framework que haga toda la compatibilidad entre navegadores por ti es simplemente poner una llamada a tu código al final del cuerpo. Esto es más rápido de ejecutar que un manejador onload porque este espera sólo a que el DOM esté listo, no a que se carguen todas las imágenes. Además, esto funciona en todos los navegadores.






Your HTML here

<script>
// self executing function here
(function() {
   // your page initialization code here
   // the DOM will be available here

})();
</script>


Para los navegadores modernos (cualquier cosa a partir de IE9 y cualquier versión de Chrome, Firefox o Safari), si quieres ser capaz de implementar un método jQuery como $(document).ready() que puedas llamar desde cualquier lugar (sin preocuparte de dónde está posicionado el script que llama), puedes simplemente usar algo como esto:

function docReady(fn) {
    // see if DOM is already available
    if (document.readyState === "complete" || document.readyState === "interactive") {
        // call on next available tick
        setTimeout(fn, 1);
    } else {
        document.addEventListener("DOMContentLoaded", fn);
    }
}    

Uso:

docReady(function() {
    // DOM is loaded and ready for manipulation here
});

Si necesitas una compatibilidad total con todos los navegadores (incluyendo versiones antiguas de IE) y no quieres esperar a window.onload, entonces probablemente deberías ir a ver cómo un framework como jQuery implementa su método $(document).ready(). Es bastante complicado dependiendo de las capacidades del navegador.

Para darte una pequeña idea de lo que hace jQuery (que funcionará donde sea que se coloque la etiqueta script).

Si es compatible, intenta el estándar:

document.addEventListener('DOMContentLoaded', fn, false);

con un retorno a:

window.addEventListener('load', fn, false )

o para versiones antiguas de IE, utiliza:

document.attachEvent("onreadystatechange", fn);

con un recurso a:

window.attachEvent("onload", fn);

Y, hay algunas soluciones en la ruta del código de IE que no sigo del todo, pero parece que tiene algo que ver con los marcos.


Aquí hay un sustituto completo del .ready() de jQuery escrito en javascript plano:

(function(funcName, baseObj) {
    // The public function name defaults to window.docReady
    // but you can pass in your own object and own function name and those will be used
    // if you want to put them in a different namespace
    funcName = funcName || "docReady";
    baseObj = baseObj || window;
    var readyList = [];
    var readyFired = false;
    var readyEventHandlersInstalled = false;

    // call this when the document is ready
    // this function protects itself against being called more than once
    function ready() {
        if (!readyFired) {
            // this must be set to true before we start calling callbacks
            readyFired = true;
            for (var i = 0; i < readyList.length; i++) {
                // if a callback here happens to add new ready handlers,
                // the docReady() function will see that it already fired
                // and will schedule the callback to run right after
                // this event loop finishes so all handlers will still execute
                // in order and no new ones will be added to the readyList
                // while we are processing the list
                readyList[i].fn.call(window, readyList[i].ctx);
            }
            // allow any closures held by these functions to free
            readyList = [];
        }
    }

    function readyStateChange() {
        if ( document.readyState === "complete" ) {
            ready();
        }
    }

    // This is the one public interface
    // docReady(fn, context);
    // the context argument is optional - if present, it will be passed
    // as an argument to the callback
    baseObj[funcName] = function(callback, context) {
        if (typeof callback !== "function") {
            throw new TypeError("callback for docReady(fn) must be a function");
        }
        // if ready has already fired, then just schedule the callback
        // to fire asynchronously, but right away
        if (readyFired) {
            setTimeout(function() {callback(context);}, 1);
            return;
        } else {
            // add the function and context to the list
            readyList.push({fn: callback, ctx: context});
        }
        // if document already ready to go, schedule the ready function to run
        if (document.readyState === "complete") {
            setTimeout(ready, 1);
        } else if (!readyEventHandlersInstalled) {
            // otherwise if we don't have event handlers installed, install them
            if (document.addEventListener) {
                // first choice is DOMContentLoaded event
                document.addEventListener("DOMContentLoaded", ready, false);
                // backup is window load event
                window.addEventListener("load", ready, false);
            } else {
                // must be IE
                document.attachEvent("onreadystatechange", readyStateChange);
                window.attachEvent("onload", ready);
            }
            readyEventHandlersInstalled = true;
        }
    }
})("docReady", window);

La última versión del código se comparte públicamente en GitHub en https://github.com/jfriend00/docReady

Uso:

// pass a function reference
docReady(fn);

// use an anonymous function
docReady(function() {
    // code here
});

// pass a function reference and a context
// the context will be passed to the function as the first argument
docReady(fn, context);

// use an anonymous function with a context
docReady(function(context) {
    // code here that can use the context argument that was passed to docReady
}, ctx);

Esto ha sido probado en:

IE6 and up
Firefox 3.6 and up
Chrome 14 and up
Safari 5.1 and up
Opera 11.6 and up
Multiple iOS devices
Multiple Android devices

Implementación de trabajo y banco de pruebas: http://jsfiddle.net/jfriend00/YfD3C/


He aquí un resumen de su funcionamiento:

  1. Crear una IIFE (expresión de función inmediatamente invocada) para que podamos tener variables de estado no públicas.
  2. Declarar una función pública docReady(fn, context).
  3. Cuando se llama a docReady(fn, context), se comprueba si el manejador ready ya se ha disparado. Si es así, programa la nueva llamada de retorno para que se dispare justo después de que este hilo de JS termine con setTimeout(fn, 1).
  4. Si el manejador listo no se ha disparado aún, entonces agrega este nuevo callback a la lista de callbacks que serán llamados más tarde.
  5. Compruebe si el documento ya está listo. Si es así, ejecuta todos los manejadores de listo.
  6. Si aún no hemos instalado escuchadores de eventos para saber cuándo el documento está listo, entonces instálelos ahora.
  7. Si document.addEventListener existe, entonces instala los manejadores de eventos usando .addEventListener() para ambos eventos "DOMContentLoaded" y "load". El "load" es un evento de respaldo por seguridad y no debería ser necesario.
  8. Si document.addEventListener no existe, entonces instale manejadores de eventos usando .attachEvent() para los eventos "onreadystatechange" y "onload".
  9. En el evento onreadystatechange, comprueba si el document.readyState === "complete" y si es así, llama a una función para disparar todos los manejadores ready.
  10. En todos los demás manejadores de eventos, llame a una función para disparar todos los manejadores de listo.
  11. En la función para llamar a todos los manejadores listos, comprueba una variable de estado para ver si ya hemos disparado. Si lo hemos hecho, no hagas nada. Si aún no hemos sido llamados, entonces haz un bucle a través de la matriz de funciones listas y llama a cada una en el orden en que fueron agregadas. Establece una bandera para indicar que todas han sido llamadas para que nunca se ejecuten más de una vez.
  12. Borra el array de funciones para que se puedan liberar los cierres que puedan estar utilizando.

Los manejadores registrados con docReady() están garantizados para ser disparados en el orden en que fueron registrados.

Si llama a docReady(fn) después de que el documento esté listo, la llamada de retorno se programará para ejecutarse tan pronto como el hilo de ejecución actual termine usando setTimeout(fn, 1). Esto permite que el código que llama siempre asuma que son devoluciones de llamada asíncronas que serán llamadas más tarde, incluso si más tarde es tan pronto como el hilo actual de JS termine y preserva el orden de llamada.

Comentarios (16)

Su método (colocar el script antes de la etiqueta de cierre del cuerpo)

<script>
   myFunction()
</script>

es una forma fiable de dar soporte a los navegadores antiguos y nuevos.

Comentarios (2)

document.ondomcontentready=function(){} debería servir, pero no es totalmente compatible con el navegador.

Parece que deberías usar jQuery min

Comentarios (5)