Ответ 1
Самая простая вещь, которую нужно сделать в отсутствие фреймворка, который обеспечивает всю кросс-браузерную совместимость для вас, - это просто вызвать вызов вашего кода в конце тела. Это выполняется быстрее, чем обработчик onload
, потому что он ожидает только готовности DOM, а не загрузки всех изображений. И это работает в любом браузере.
<!doctype html>
<html>
<head>
</head>
<body>
Your HTML here
<script>
// self executing function here
(function() {
// your page initialization code here
// the DOM will be available here
})();
</script>
</body>
</html>
Для современных браузеров (от 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 .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/
Вот краткое изложение того, как это работает:
- Создайте IIFE (немедленно вызванное выражение функции), чтобы у нас могли быть непубличные переменные состояния.
- Объявите публичную функцию
docReady(fn, context)
- Когда вызывается
docReady(fn, context)
, проверьте, запущен ли обработчик готовности. Если это так, просто запланируйте запуск только что добавленного обратного вызова сразу после того, как этот поток JS завершится сsetTimeout(fn, 1)
. - Если обработчик готовности еще не сработал, добавьте этот новый обратный вызов в список обратных вызовов, которые будут вызваны позже.
- Проверьте, готов ли документ. Если это так, выполните все готовые обработчики.
- Если мы еще не установили прослушиватели событий, чтобы знать, когда документ будет готов, установите их сейчас.
- Если существует
document.addEventListener
, то установите обработчики событий, используя.addEventListener()
для событий"DOMContentLoaded"
и"load"
. "Загрузка" является резервным событием для обеспечения безопасности и не должна быть необходима. - Если
document.addEventListener
не существует, установите обработчики событий, используя.attachEvent()
для событий"onreadystatechange"
и"onload"
. - В событии
onreadystatechange
проверьте, есть лиdocument.readyState === "complete"
, и если да, то вызовите функцию для запуска всех готовых обработчиков. - Во всех других обработчиках событий вызовите функцию для запуска всех готовых обработчиков.
- В функции, чтобы вызвать все готовые обработчики, проверьте переменную состояния, чтобы увидеть, запущены ли мы уже. Если у нас есть, ничего не делать. Если мы еще не были вызваны, то перебираем массив готовых функций и вызываем каждую из них в порядке их добавления. Установите флаг, чтобы указать, что все они были вызваны, чтобы они никогда не выполнялись более одного раза.
- Очистите массив функций, чтобы можно было освободить все замыкания, которые они могут использовать.
Обработчики, зарегистрированные в docReady()
, гарантированно будут запускаться в порядке, в котором они были зарегистрированы.
Если вы вызываете docReady(fn)
после того, как документ уже готов, обратный вызов будет запланирован на выполнение, как только текущий поток выполнения завершит работу, используя setTimeout(fn, 1)
. Это позволяет вызывающему коду всегда предполагать, что они являются асинхронными обратными вызовами, которые будут вызваны позже, даже если позднее это произойдет, как только текущий поток JS завершит свою работу, и он сохранит порядок вызова.