L'équivalent en JavaScript pur de $.ready() de jQuery - comment appeler une fonction lorsque la page/le DOM est prêt(e) à l'accueillir.

Bon, c&#8217est peut-être une question idiote, mais je suis sûr qu&#8217il y a beaucoup d&#8217autres personnes qui posent la même question de temps en temps. Moi, je veux juste en être sûr à 100%, de toute façon. Avec jQuery, nous connaissons tous la merveilleuse fonction

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

Cependant, disons que je veux exécuter une fonction écrite en JavaScript standard, sans bibliothèque pour la soutenir, et que je veux lancer une fonction dès que la page est prête à la gérer. Quelle est la bonne façon d'aborder cette question ?

Je sais que je peux le faire :

window.onload="myFunction()";

...ou je peux utiliser la balise body :

<body onload="myFunction()">

...ou je peux même essayer en bas de la page après tout, mais la balise de fin body ou html comme :

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

Quelle est une méthode compatible avec tous les navigateurs (anciens/nouveaux) permettant de lancer une ou plusieurs fonctions à la manière de jQuery ($.ready()`) ?

Solution

La chose la plus simple à faire en l'absence d'un framework qui s'occupe de la compatibilité entre navigateurs est de placer un appel à votre code à la fin du corps. C'est plus rapide à exécuter qu'un gestionnaire onload parce que celui-ci attend seulement que le DOM soit prêt, et non que toutes les images soient chargées. Et cela fonctionne dans tous les navigateurs.






Your HTML here

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

})();
</script>


Pour les navigateurs modernes (à partir d'IE9 et plus récent et toute version de Chrome, Firefox ou Safari), si vous voulez être en mesure d'implémenter une méthode de type jQuery $(document).ready() que vous pouvez appeler de n'importe où (sans vous soucier de la position du script appelant), vous pouvez simplement utiliser quelque chose comme ceci :

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);
    }
}    

Utilisation :

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

Si vous avez besoin d'une compatibilité totale entre navigateurs (y compris les anciennes versions d'IE) et que vous ne voulez pas attendre window.onload, vous devriez probablement regarder comment un framework comme jQuery implémente sa méthode $(document).ready(). C'est assez complexe et dépend des capacités du navigateur.

Pour vous donner une petite idée de ce que fait jQuery (qui fonctionnera partout où la balise script est placée).

S'il est supporté, il essaie le standard :

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

avec une solution de repli vers :

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

ou pour les anciennes versions d'IE, il utilise :

document.attachEvent("onreadystatechange", fn);

avec une solution de repli vers :

window.attachEvent("onload", fn);

Et il existe des solutions de contournement dans le chemin de code d'IE que je ne comprends pas bien, mais il semble que cela ait quelque chose à voir avec les cadres.


Voici un substitut complet du .ready() de jQuery écrit en javascript pur :

(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 dernière version du code est partagée publiquement sur GitHub à l'adresse https://github.com/jfriend00/docReady.

Utilisation :

// 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);

Ceci a été testé dans :

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

Mise en oeuvre et banc d'essai : http://jsfiddle.net/jfriend00/YfD3C/


Voici un résumé de son fonctionnement :

  1. Créer un IIFE (expression de fonction immédiatement invoquée) afin d'avoir des variables d'état non publiques.
  2. Déclarer une fonction publique docReady(fn, context).
  3. Lorsque docReady(fn, context) est appelée, vérifiez si le gestionnaire ready a déjà été lancé. Si c'est le cas, il suffit de programmer le nouveau callback pour qu'il se déclenche juste après la fin de ce thread de JS avec setTimeout(fn, 1).
  4. Si le ready handler n'a pas encore été déclenché, alors ajoutez ce nouveau callback à la liste des callbacks qui seront appelés plus tard.
  5. Vérifiez si le document est déjà prêt. Si oui, exécutez tous les handlers ready.
  6. Si nous n'avons pas encore installé d'écouteurs d'événements pour savoir quand le document est prêt, installez-les maintenant.
  7. Si document.addEventListener existe, alors installez les gestionnaires d'événements en utilisant .addEventListener() pour les deux événements "DOMContentLoaded" et "load". L'événement "load" est un événement de secours pour la sécurité et ne devrait pas être nécessaire.
  8. Si document.addEventListener n'existe pas, alors installez des gestionnaires d'événements en utilisant .attachEvent() pour les événements "onreadystatechange" et "onload".
  9. Dans l'événement onreadystatechange, vérifiez si le document.readyState === "complete" et si c'est le cas, appelez une fonction pour lancer tous les handlers ready.
  10. Dans tous les autres gestionnaires d'événements, appelez une fonction pour activer tous les gestionnaires de documents prêts.
  11. Dans la fonction qui appelle tous les gestionnaires prêts, vérifiez une variable d'état pour voir si nous avons déjà déclenché. Si c'est le cas, ne faites rien. Si nous n'avons pas encore été appelés, alors bouclez à travers le tableau des fonctions prêtes et appelez chacune d'entre elles dans l'ordre où elles ont été ajoutées. Définissez un drapeau pour indiquer qu'elles ont toutes été appelées afin qu'elles ne soient jamais exécutées plus d'une fois.
  12. Effacez le tableau des fonctions pour que les fermetures qu'elles utilisent puissent être libérées.

Les gestionnaires enregistrés avec docReady() sont garantis d'être lancés dans l'ordre où ils ont été enregistrés.

Si vous appelez docReady(fn) alors que le document est déjà prêt, le callback sera programmé pour s'exécuter dès que le fil d'exécution actuel sera terminé en utilisant setTimeout(fn, 1). Cela permet au code appelant de toujours supposer qu'il s'agit de callbacks asynchrones qui seront appelés plus tard, même si plus tard est dès que le fil d'exécution actuel de JS se termine et cela préserve l'ordre d'appel.

Commentaires (16)

Votre méthode (placer le script avant la balise de fermeture du corps)

<script>
   myFunction()
</script>

est un moyen fiable de prendre en charge les anciens et les nouveaux navigateurs.

Commentaires (2)

document.ondomcontentready=function(){} devrait faire l'affaire, mais il n'a pas une compatibilité totale avec les navigateurs.

Il semble que vous devriez simplement utiliser jQuery min.

Commentaires (5)