Чист JavaScript еквивалент на jQuery's $.ready() - как да извикате функция, когато страницата/DOM е готова за нея

Добре, това може да е просто глупав въпрос, въпреки че съм сигурен, че има много други хора, които от време на време задават същия въпрос. Аз просто искам да съм 100% сигурен за това така или иначе. С jQuery всички знаем чудесното

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

Нека'обаче да кажем, че искам да стартирам функция, която е написана на стандартен JavaScript, без да е подкрепена от библиотека, и че искам да стартирам функцията веднага щом страницата е готова да я обработи. Какъв е правилният начин да подходим към това?

Знам, че мога да направя:

window.onload="myFunction()";

...или мога да използвам тага body:

<body onload="myFunction()">

...или дори мога да опитам в долната част на страницата след всичко, но след края на тага body или html, като например:

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

Какъв е методът за издаване на една или повече функции, съвместим с различни браузъри (стари/нови), по начин, подобен на jQuery's $.ready()?

Решение

Най-простото нещо, което можете да направите, ако нямате рамка, която да се грижи за съвместимостта с различни браузъри вместо вас, е просто да поставите извикване на вашия код в края на тялото. Това е по-бързо за изпълнение от обработката onload, тъй като се чака само DOM да е готов, а не всички изображения да се заредят. И това работи във всеки браузър.






Your HTML here

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

})();
</script>


За съвременните браузъри (всички версии от IE9 и по-нови и всички версии на Chrome, Firefox или Safari), ако искате да можете да приложите подобен на jQuery метод $(document).ready(), който можете да извикате отвсякъде (без да се притеснявате къде е позициониран извикващият скрипт), можете просто да използвате нещо подобно:

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

Употреба:

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

Ако се нуждаете от пълна съвместимост с различни браузъри (включително стари версии на IE) и не искате да чакате за window.onload, тогава вероятно трябва да разгледате как фреймуърк като jQuery реализира своя метод $(document).ready(). Това е доста сложно в зависимост от възможностите на браузъра.

За да ви дам малка представа какво прави jQuery (което ще работи навсякъде, където е поставен тагът script).

Ако се поддържа, той опитва стандартния:

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

с резервен вариант:

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

или за по-стари версии на IE използва:

document.attachEvent("onreadystatechange", fn);

с резервен вариант на:

window.attachEvent("onload", fn);

Има и някои заобикаляния на кода на IE, които не разбирам, но изглежда, че са свързани с рамки.


Ето и пълен заместител на jQuery's .ready(), написан на чист 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);

Последната версия на кода е публично достъпна в GitHub на адрес https://github.com/jfriend00/docReady

Използване:

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

Това е тествано в:

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

Работна реализация и тестова база: http://jsfiddle.net/jfriend00/YfD3C/


Ето резюме на начина на работа:

  1. Създайте IIFE (израз на незабавно извикана функция), за да можем да имаме непублични променливи на състоянието.
  2. Декларирайте публична функция docReady(fn, context)
  3. Когато се извика функцията docReady(fn, context), проверете дали обработващата функция ready вече се е задействала. Ако е така, просто планирайте новодобавената обратна връзка да се задейства веднага след като тази нишка на JS приключи с setTimeout(fn, 1).
  4. Ако обработващото устройство ready все още не се е задействало, добавете това ново повикване към списъка с повиквания, които ще бъдат извикани по-късно.
  5. Проверете дали документът вече е готов. Ако това е така, изпълнете всички готови обработчици.
  6. Ако все още не сме'инсталирали слушатели на събития, за да знаем кога документът става готов, тогава ги инсталирайте сега.
  7. Ако съществува document.addEventListener, тогава инсталирайте обработчици на събития с помощта на .addEventListener() за двете събития "DOMContentLoaded" и "load". Събитието "load" е резервно събитие за безопасност и не трябва да е необходимо.
  8. Ако document.addEventListener не съществува, инсталирайте обработчици на събития с помощта на .attachEvent() за събитията "onreadystatechange" и "onload".
  9. В събитието onreadystatechange проверете дали document.readyState === "complete" и ако е така, извикайте функция, за да задействате всички обработчици за готовност.
  10. Във всички останали обработчици на събития извикайте функция, която да задейства всички обработчици за готовност.
  11. Във функцията за извикване на всички готови обработчици проверете променливата на състоянието, за да видите дали вече не сме'изстреляли. Ако сме, не правете нищо. Ако все още не сме били извикани, направете цикъл през масива от готови функции и извикайте всяка от тях по реда, по който са добавени. Задайте флаг, който да показва, че всички те са били извикани, така че никога да не се изпълняват повече от веднъж.
  12. Изчистете масива на функциите, за да могат да бъдат освободени всички затварящи устройства, които те използват.

Обслужващите функции, регистрирани с docReady(), гарантирано ще бъдат изпълнени в реда, в който са регистрирани.

Ако извикате docReady(fn), след като документът вече е готов, обратната връзка ще бъде планирана да се изпълни веднага след като текущата нишка на изпълнение приключи с помощта на setTimeout(fn, 1). Това позволява на извикващия код винаги да приема, че това са асинхронни обратни извиквания, които ще бъдат извикани по-късно, дори ако по-късно е веднага щом приключи текущата нишка на JS, и запазва реда на извикване.

Коментари (16)

Вашият метод (поставяне на скрипта преди затварящия таг на тялото)

<script>
   myFunction()
</script>

е надежден начин за поддръжка на стари и нови браузъри.

Коментари (2)

document.ondomcontentready=function(){} би трябвало да свърши работа, но няма пълна съвместимост с браузъра.

Изглежда, че трябва просто да използвате jQuery min

Коментари (5)