Pure JavaScript vastaa jQuery'n $.ready() - miten kutsua toimintoa, kun sivu/DOM on valmis sitä varten?

Okei, tämä saattaa olla vain typerä kysymys, vaikka olen varma, että monet muut ihmiset kysyvät samaa kysymystä aika ajoin. Itse haluan vain olla 100% varma asiasta kumminkin. JQueryn kanssa me kaikki tiedämme ihanan

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

Sanotaan kuitenkin, että haluan ajaa funktiota, joka on kirjoitettu tavallisella JavaScriptillä ilman kirjastoa sen tukena, ja että haluan käynnistää funktion heti, kun sivu on valmis käsittelemään sitä. Mikä'on oikea tapa lähestyä tätä?

Tiedän, että voin tehdä näin:

window.onload="myFunction()";

...tai voin käyttää body-tagia:

<body onload="myFunction()">

...tai voin jopa kokeilla sivun alareunassa kaiken jälkeen, mutta body- tai html-tunnisteen lopussa, kuten:

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

Mikä on selainten(vanha/uusi)-yhteensopiva tapa antaa yksi tai useampi funktio jQueryn tapaan $.ready()?

Ratkaisu

Yksinkertaisinta on vain laittaa kutsu koodiin rungon loppuun, jos ei ole olemassa kehystä, joka huolehtii selaintenvälisestä yhteensopivuudesta puolestasi. Tämä on nopeampi toteuttaa kuin onload-käsittelijä, koska tämä odottaa vain DOM:n olevan valmis, ei kaikkien kuvien latautumista. Ja tämä toimii kaikissa selaimissa.






Your HTML here

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

})();
</script>


Nykyaikaisissa selaimissa (kaikki IE9:stä ja uudemmista sekä kaikki Chromen, Firefoxin tai Safarin versiot), jos haluat toteuttaa jQueryn kaltaisen $(document).ready()-metodin, jota voit kutsua mistä tahansa (huolehtimatta siitä, missä kutsuva skripti on sijoitettu), voit käyttää jotakin tämän kaltaista:

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

Käyttö:

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

Jos tarvitset täydellistä selainten välistä yhteensopivuutta (mukaan lukien IE:n vanhat versiot) etkä halua odottaa window.onload-menetelmää, sinun kannattaa todennäköisesti katsoa, miten jQueryn kaltainen kehys toteuttaa $(document).ready()-metodinsa. Se on melko monimutkaista selaimen ominaisuuksista riippuen.

Annan sinulle pienen käsityksen siitä, mitä jQuery tekee (joka toimii missä tahansa script-tagi on sijoitettu).

Jos se on tuettu, se kokeilee standardia:

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

ja varasuunnitelmana on:

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

tai IE:n vanhemmissa versioissa se käyttää:

document.attachEvent("onreadystatechange", fn);

ja varajärjestelmä on:

window.attachEvent("onload", fn);

Ja IE:n koodipolussa on joitakin kiertoteitä, joita en oikein ymmärrä, mutta näyttää siltä, että se liittyy jotenkin kehyksiin.


Tässä on jQueryn täydellinen korvike .ready(), joka on kirjoitettu tavallisella javascriptillä:

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

Koodin uusin versio on jaettu julkisesti GitHubissa osoitteessa https://github.com/jfriend00/docReady.

Käyttö:

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

Tämä on testattu:

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

Toimiva toteutus ja testausympäristö: http://jsfiddle.net/jfriend00/YfD3C/


Tässä on yhteenveto siitä, miten se toimii:

  1. Luo IIFE (välittömästi kutsuttu funktiolauseke), jotta meillä voi olla ei-julkisia tilamuuttujia.
  2. Julistetaan julkinen funktio docReady(fn, context).
  3. Kun docReady(fn, context) kutsutaan, tarkista, onko valmis käsittelijä jo lauennut. Jos on, ajoita juuri lisätty takaisinkutsu laukeamaan heti sen jälkeen, kun tämä JS-säie on päättynyt, komennolla setTimeout(fn, 1).
  4. Jos valmis-käsittelijä ei ole vielä lauennut, lisää tämä uusi takaisinkutsu myöhemmin kutsuttavien takaisinkutsujen luetteloon.
  5. Tarkista, onko asiakirja jo valmis. Jos näin on, suorita kaikki ready-käsittelijät.
  6. Jos emme ole vielä asentaneet tapahtumakuuntelijoita, jotta tiedämme, milloin dokumentti on valmis, asenna ne nyt.
  7. Jos document.addEventListener on olemassa, asenna tapahtumakäsittelijät käyttämällä .addEventListener() sekä "DOMContentLoaded"- että "load"-tapahtumille. "load" on varmuuden vuoksi varatapahtuma, eikä sitä pitäisi tarvita.
  8. Jos document.addEventListener ei ole olemassa, asenna tapahtumankäsittelijät käyttäen .attachEvent() tapahtumille "onreadystatechange" ja "onload".
  9. Tarkista onreadystatechange-tapahtumassa, onko document.readyState === "complete", ja jos on, kutsu funktiota, joka käynnistää kaikki ready-käsittelijät.
  10. Kutsu kaikissa muissa tapahtumankäsittelijöissä funktiota, joka laukaisee kaikki ready-käsittelijät.
  11. Tarkista funktiossa, joka kutsuu kaikkia valmiiden käsittelijöitä, tilamuuttujasta, onko me'n jo laukaistu. Jos olemme, älä tee mitään. Jos meitä ei ole vielä kutsuttu, käy silmukalla läpi valmiiden funktioiden joukko ja kutsu jokaista funktiota siinä järjestyksessä kuin ne on lisätty. Aseta lippu, joka osoittaa, että näitä kaikkia on kutsuttu, jotta niitä ei koskaan suoriteta useammin kuin kerran.
  12. Tyhjennä funktioiden joukko, jotta niiden mahdollisesti käyttämät sulkimet voidaan vapauttaa.

docReady():lla rekisteröidyt käsittelijät aktivoidaan taatusti siinä järjestyksessä kuin ne on rekisteröity.

Jos kutsut docReady(fn) sen jälkeen, kun dokumentti on jo valmis, takaisinkutsu ajoitetaan suoritettavaksi heti, kun nykyinen suoritussäie päättyy setTimeout(fn, 1):n avulla. Näin kutsuva koodi voi aina olettaa, että kyseessä ovat asynkroniset takaisinkutsut, joita kutsutaan myöhemmin, vaikka "myöhemmin" olisikin heti, kun nykyinen JS-säie päättyy, ja se säilyttää kutsujärjestyksen.

Kommentit (16)

Menetelmäsi (komentosarjan sijoittaminen ennen sulkevaa body-tagia)

<script>
   myFunction()
</script>

on luotettava tapa tukea vanhoja ja uusia selaimia.

Kommentit (2)

document.ondomcontentready=function(){} pitäisi toimia, mutta se ei ole täysin yhteensopiva selaimen kanssa.

Näyttää siltä, että sinun pitäisi vain käyttää jQuery min.

Kommentit (5)