Ren JavaScript-ækvivalent til jQuery's $.ready() - hvordan man kalder en funktion, når siden/DOM er klar til det

Okay, det er måske bare et dumt spørgsmål, selv om jeg er sikker på, at der er masser af andre, der stiller det samme spørgsmål fra tid til anden. Mig, jeg vil bare gerne være 100% sikker på det uanset hvad. Med jQuery kender vi alle den vidunderlige

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

Men lad os sige, at jeg ønsker at køre en funktion, der er skrevet i standard JavaScript uden noget bibliotek bagved, og at jeg ønsker at starte en funktion, så snart siden er klar til at håndtere den. Hvad'er den korrekte måde at gribe dette an på?

Jeg ved, at jeg kan gøre det:

window.onload="myFunction()";

...eller jeg kan bruge body-tag:

<body onload="myFunction()">

...eller jeg kan endda prøve nederst på siden efter alt, men i slutningen af body eller html tagget som f.eks:

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

Hvad er en cross-browser(gammel/ny)-kompatibel metode til at udstede en eller flere funktioner på en måde som jQuery's $.ready()?

Løsning

Det enkleste at gøre, hvis du ikke har en ramme, der klarer kompatibiliteten på tværs af browsere for dig, er blot at indsætte et kald til din kode i slutningen af kroppen. Dette er hurtigere at udføre end en onload-handler, fordi denne kun venter på at DOM'en er klar, ikke på at alle billeder indlæses. Og det virker i alle browsere.






Your HTML here

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

})();
</script>


For moderne browsere (alt fra IE9 og nyere og enhver version af Chrome, Firefox eller Safari), hvis du ønsker at kunne implementere en jQuery-lignende $(document).ready()-metode, som du kan kalde fra hvor som helst (uden at bekymre dig om, hvor det kaldende script er placeret), kan du bare bruge noget som dette:

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

Anvendelse:

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

Hvis du har brug for fuld kompatibilitet på tværs af browsere (herunder gamle versioner af IE), og du ikke ønsker at vente på window.onload, så bør du nok kigge på, hvordan et framework som jQuery implementerer sin $(document).ready()-metode. Det'er ret indviklet afhængig af browserens muligheder.

For at give dig en lille idé om hvad jQuery gør (som vil virke uanset hvor script tagget er placeret).

Hvis den understøttes, prøver den standarden:

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

med en fallback til:

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

eller for ældre versioner af IE, bruger den:

document.attachEvent("onreadystatechange", fn);

med en fallback til:

window.attachEvent("onload", fn);

Og, der er nogle work-arounds i IE kode stien, som jeg ikke helt følger, men det ser ud til at det har noget at gøre med frames.


Her er en komplet erstatning for jQuery's .ready() skrevet i almindeligt javascript:

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

Den seneste version af koden er delt offentligt på GitHub på https://github.com/jfriend00/docReady

Anvendelse:

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

Dette er blevet testet i:

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

Arbejdende implementering og testbed: http://jsfiddle.net/jfriend00/YfD3C/


Her er et resumé af, hvordan det fungerer:

  1. Opret en IIFE (immediately invoked function expression), så vi kan have ikke-offentlige tilstandsvariabler.
  2. Deklarere en offentlig funktion docReady(fn, context)
  3. Når docReady(fn, context) kaldes, kontrolleres det, om ready-handleren allerede er blevet udløst. Hvis det er tilfældet, skal du blot planlægge den nyligt tilføjede callback til at blive udløst lige efter, at denne JS-tråd er afsluttet med setTimeout(fn, 1).
  4. Hvis ready-handleren ikke allerede er blevet affyret, skal du tilføje denne nye callback til listen over callbacks, der skal kaldes senere.
  5. Kontroller, om dokumentet allerede er klar. Hvis det er tilfældet, udføres alle ready handlers.
  6. Hvis vi endnu ikke har installeret begivenhedslyttere til at vide, hvornår dokumentet bliver klar, skal du installere dem nu.
  7. Hvis document.addEventListener findes, installer da event handlers ved hjælp af .addEventListener() for både &"DOMContentLoaded" og "load" events. "load" er en backup-hændelse af sikkerhedshensyn og bør ikke være nødvendig.
  8. Hvis document.addEventListener ikke eksisterer, skal du installere event handlers ved hjælp af .attachEvent() for &"onreadystatechange" og "onload" events.
  9. I hændelsen onreadystatechange kontrolleres det, om document.readyState === "complete", og hvis det er tilfældet, kaldes en funktion, der affyrer alle ready-handlerne.
  10. I alle de andre event handlers kalder du en funktion til at affyre alle ready handlers.
  11. I funktionen til at kalde alle ready handlers kontrollerer du en tilstandsvariabel for at se, om vi'allerede har fyret. Hvis vi har, skal du ikke gøre noget. Hvis vi endnu ikke er blevet kaldt, skal du gennemløbe arrayet af ready-funktioner og kalde hver enkelt funktion i den rækkefølge, de blev tilføjet. Sæt et flag for at angive, at de alle er blevet kaldt, så de aldrig bliver udført mere end én gang.
  12. Ryd funktionsarrayet, så eventuelle lukninger, som de måtte bruge, kan frigives.

Handlers registreret med docReady() er garanteret at blive affyret i den rækkefølge, de blev registreret.

Hvis du kalder docReady(fn) efter at dokumentet allerede er klar, vil callbacken blive planlagt til at blive udført, så snart den aktuelle udførelsestråd er færdig med setTimeout(fn, 1). Dette gør det muligt for den kaldende kode altid at antage, at der er tale om asynkrone callbacks, som vil blive kaldt senere, selv om senere er så snart den aktuelle JS-tråd er færdig, og det bevarer kaldsrækkefølgen.

Kommentarer (16)

Din metode (placering af scriptet før det afsluttende body tag)

<script>
   myFunction()
</script>

er en pålidelig måde at understøtte gamle og nye browsere på.

Kommentarer (2)

document.ondomcontentready=function(){} skulle gøre tricket, men den er ikke fuldt ud kompatibel med browsere.

Det ser ud til, at du bare skal bruge jQuery min

Kommentarer (5)