На предыдущем
занятии мы узнали о событиях, которые происходят во время загрузки HTML-документа:
DOMContentLoaded, load, unload, beforeunload
Но, что если мы
хотим отследить загрузку отдельных элементов документа, например, изображения
или скрипта:
img, script
Оказывается для
всех элементов, имеющих атрибут src, генерируется событие
load
при завершении
загрузки содержимого элемента. Например, повесим обработчик такого события на
загрузку изображения:
<!DOCTYPE html>
<html>
<head>
<title>Уроки по JavaScript</title>
</head>
<body>
<p><img id="image" src="/htm/javascript_dom/files/images/winter.jpg?nocashe=1" />
<script>
image.onload = function(event) {
console.log(`Изображение: ${image.offsetWidth}x${image.offsetHeight}`);
}
</script>
</body>
</html>
Теперь после
загрузки изображения мы увидим в консоли его размеры. То же самое можно сделать
и со скриптом:
<script src="js/ex1.js" onload="execute()"></script>
А в скрипте выше
прописать эту функцию:
function execute() {
console.log("скрипт загружен");
}
При обновлении
документа мы увидим сообщение, что скрипт загружен. Для чего нам может
понадобиться обработчик события load для скриптов? Например, если нужно
вызвать некоторую функцию, объявленную в ex1.js. Очевидно, это
можно сделать только после его загрузки:
function execute() {
__fromJS();
}
Причем, во всех
случаях обработчик execute() должен быть объявлен до строчки
<script src="js/ex1.js" onload="execute()"></script>
Или же, мы можем
сделать так: создать скрипт динамически:
let script = document.createElement('script');
script.src = "js/ex1.js";
script.onload = execute;
document.head.append(script);
И тогда
местоположение обработчика не будет иметь значения.
Но, как вы
понимаете, при загрузке ресурсов могут происходить ошибки. Например, если
указать неверный путь к файлу:
script.src = "js/ex11.js";
то при загрузке
произойдет ошибка и событие load не сгенерируется для данного элемента. Для
обработки таких ошибок можно использовать событие
error
следующим
образом:
script.onerror = execute_error;
и пропишем обработчик:
function execute_error() {
console.log("ошибка при загрузке ресурса");
}
Теперь, при
обновлении документа, мы увидим строчку с сообщением об ошибке. В идеале здесь
можно выполнить некоторые действия для минимизации вреда от такой ситуации.
Аналогичный
обработчик можно повесить и для изображения:
image.onerror = () => console.log("error load image");
async, defer
Во второй части
занятия поговорим об атрибутах тега <script>. Они
добавляют гибкости при подключении внешних скриптов к HTML-документу. И
начнем с атрибута defer. Представьте, что
у нас имеется внешний скрипт, который имеет большой объем и долго загружается с
сервера:
<p>Текст до скрипта
<script src="http://funnyballs.ru/long_script.js">
</script>
<p>Текст после скрипта
Тогда мы увидим
сначала первый абзац и только после загрузки – второй абзац. То есть, наш HTML-документ будет
подвисать и ждать окончания загрузки внешнего ресурса. Если у нас всего один
такой внешний скрипт, то в самом простом случае его можно просто расположить в
конце документа:
<p>Текст до скрипта
<script defer src="http://funnyballs.ru/long_script.js">
</script>
<p>Текст после скрипта
Но это далеко не
самое лучшее решение. В таких ситуациях можно воспользоваться атрибутом defer, который
сообщает браузеру, чтобы он загружал скрипт в фоновом режиме, продолжая
загружать основной HTML-документ. Как только скрипт загрузится он начнет
выполняться.
Если добавить
этот атрибут в наш тег:
<p>Текст до скрипта
<script defer src="http://funnyballs.ru/long_script.js">
</script>
<p>Текст после скрипта
то он выполнится
до появления какого-либо абзаца, так как браузер загрузит и выполнит его
быстрее, чем отобразит теги p. Это как раз и есть эффект атрибута defer – браузер обрабатывал
скрипт в фоне. Причем, скрипты с этим атрибутом выполнятся только после того,
как браузер целиком загрузит всю страницу и построит DOM-дерево, но до
возникновения события DOMContentLoaded.
Мы это можем увидеть на следующем примере:
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log("DOMContentLoaded");
});
</script>
Далее, если на
нашей странице имеется несколько скриптов с атрибутом defer:
<script defer src=" http://funnyballs.ru/long_script.js"></script>
<script defer src="js/ex2.js"></script>
то они будут
выполняться в порядке записи в HTML-документе, то есть, сначала выполнится ex1.js, а затем, ex2.js вне зависимости
от того, который из них будет загружен раньше. Например, если скрипт ex1.js окажется
большим, а ex2.js – маленьким, то
выполнение второго скрипта будет отложено до тех пор, пока не загрузится первый
большой скрипт и не выполнится.
Иногда такое
поведение не то, что нам бы хотелось. Допустим, что для нас было бы лучше, если
бы скрипты запускались в порядке их загрузки. В этом случае следует
воспользоваться атрибутом
async
который делает
скрипт совершенно независимым от других скриптов, а также от события DOMContentLoaded: оно может наступить и раньше
и позже, в зависимости от процесса загрузки скрипта.
Такие асинхронные
скрипты очень полезны для добавления на страницу сторонних скриптов: счётчиков,
рекламы и т.п. Они не зависят от других скриптов, и не вызывают задержек при
загрузке и отображении всего HTML-документа.
Кстати, если в JavaScript-программе скрипт
создается динамически, как в одном из наших примеров:
let script = document.createElement('script');
script.src = "js/ex1.js";
script.onload = execute;
document.head.append(script);
то он запускается в асинхронном режиме.
Вот так работают
атрибуты
async,
defer
и события
load, error
для отслеживания
загрузки ресурсов отдельных элементов.