Все, что нужно знать о теге script
Как вы, наверное, знаете, тег Script используется для подключения JavaScript, который должен быть запущен на странице. Тег сценария может либо включать непосредственно JavaScript код, либо указывать на URL, откуда сценарий должен быть загружен.
Теги выполняются в порядке их появления. Это должно быть интуитивно понятно, когда вы читаете этот код:
<script>
var x = 3;
</script>
<script>
alert(x);
// Will alert '3';
</script>
Это менее интуитивно (но не менее верно) при работе с внешними ресурсами.
<script src=\"//typekit.com/fj3j1j2.js\"></script>
<script src=\"//my.site/script.js\"></script>
Это аналогично работает для комбинаций локальных и удаленных сценариев.
Функционально это означает, что вы можете существенно замедлить ваш сайт, если у вас есть медленные сценарии с загрузкой в начале страницы. Это также означает, что сценарии, которые появляются позже на странице, могут зависеть от элементов сценариев, которые выполнились раньше.
Элементы на странице не будут отображаться, пока все теги сценария, предшествующие им, не загрузятся и не выполнятся. То есть, вы можете изменить страницу, прежде чем она загрузится, если вы готовы пожертвовать производительностью. Это не относится, к добавлению тега в DOM после того, как страница начала загружаться с помощью document.appendChild или чего-то подобного. Эти теги будут загружаться каждый раз, когда браузер сочтет нужным, и в произвольном порядке.
Когда сценарий выполняется, доступно только то, что находится над ним в DOM.
<html>
<head>
<script>
// document.head is available
// document.body is not!
</script>
</head>
<body>
<script>
// document.head is available
// document.body is available
</script>
</body>
</html>
Вы можете подумать о HTML-анализаторе, как о прохождении по документу тег за тегом, по ходу выполняя все JavaScript. Это означает, что DOM узлы доступны для вашего JavaScript (через querySelectorAll, JQuery и т.п.), только если их тег появляется в документе раньше, чем тег сценария.
Полезным следствием этого является то, что document.head доступен практически всегда, в любом JavaScript, а document.body доступен только если ваш скрипт находится внутри тега или после body.
Асинхронные скрипты async and defer
HTML5 добавил пару инструментов для управления выполнением сценариев:
- Async означает \"выполнить это в любое время\". Более конкретно это означает: Мне все равно, что вы выполняете это после того, как вся страница была загружена, и все остальные сценарии были выполнены. Это очень полезно для кодов аналитики отслеживания, так как никакой другой код на странице не зависит от их исполнения. Определение важной переменной или функции в async коде - это плохая идея, так как вы действительно не знаете, когда именно он сработает.
- Defer означает \"ждать анализатор, чтобы закончить и выполнить\". Это примерно равносильно подвязке сценария к событию DOMContentLoaded или использованию jQuery.ready. Когда код выполняется, все в DOM будет доступно для использования. В отличие от async, defer код будет работать по порядку, в котором он расположен на HTML странице, он просто откладывается до тех пор, пока HTML не будет полностью проанализирован.
Атрибут Type
Исторически (со времен Netscape 2), было абсолютно неважно, указали ли вы type=\"text/javascript\" в тегах сценария или просто оставили его пустым. Если вы установили какой-либо тип MIME, который не является разновидностью JavaScript как type, браузер все равно не будет выполнять его. Это может быть здорово, когда вы хотите определить свой собственный язык:
<script type=\"text/emerald\">
make a social network
but for cats
</script>
Фактическое выполнение этого кода зависит от вас, т.е., вы можете использовать что-то вроде этого:
<script>
var codez = document.querySelectorAll('script[type=\"text/emerald"]');
for (var i=0; i < codez.length; i++)
runEmeraldCode(codez[i].innerHTML);
</script>
Если у вас появится такое желание, вы можете также переопределить тип по умолчанию для каждого сценария на странице, используя тег meta:
<meta http-equiv=\"Content-Script-Type\" content=\"text/vbscript\">
Или заголовок Content-Script-Type.
integrity
Атрибут integrity является частью новой спецификации Subresource. Он позволяет получить хэш контента, который содержит файл сценария. Это полезно для предотвращения изменения содержимого тега script извне. В мире SSL, это необходимо, если вы загружаете скрипт из внешнего источника, который вы не контролируете, например, code.jquery.com.
Если вы решите использовать этот инструмент, вы должны указать тип хэша который вы используете, и значение хэша, разделив их дефисом. Это выглядит следующим образом:
<script
src=\"//code.jquery.com/jquery.js\"
integrity=\"sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC\">
</script>
Crossorigin
Он не полностью стандартизирован, но поддерживается некоторыми браузерами. Основная идея заключается в том, что браузер не любит позволять вам распоряжаться ресурсами, загруженными не с текущей страницы, а из другого \"источника\". (Происхождение определяется как сочетание протокола, имени хоста и порта страницы. т.е. http://google.com:80).
Его связь с тегами сценария несколько условна. Идея заключается в том, что если применить обработчик к событию window.onerror, этот обработчик обеспечивается некоторой информацией о странице и сценарии если вы загружаете код с внешнего сайта. В защищенных браузерах эта информация не передается, если не указан атрибут crossorigin.
Crossorigin не магический хак безопасности, он всего лишь позволяет настроить браузер, чтобы выполнить CORS проверку доступа на запрос OPTIONS и проверку заголовков Access-Control.
document.currentScript
Он не поддерживается IE, что несколько снижает его эффективность, но свойство document.currentScript указывает на текущий выполняемый сценарий. Это может быть очень полезно, если вы хотите вытащить атрибуты из текущего тега script.
onafterscriptexecute
Это супер-бесполезная вещь, потому что она поддерживается только Firefox. Этот атрибут наряду с onbeforescriptexecute позволяет указать событие, которое будет выполняться до и после каждого сценария на странице, что, в общем полезно.
Если вам интересно, объекты событий включают в себя ссылку на выполняемый сценарий, поэтому событие before может отменить выполнение с помощью preventDefault().
for/event
На сегодняшний день в спецификации HTML5 включены редкие, специфические IE методы привязки кода к событию. Вы можете использовать это, чтобы тег сценария не выполнялся, пока страница не загрузится:
<script for=\"window\" event=\"onload\">
alert(\"Hi!\")
</script>
NOSCRIPT
В это трудно поверить, но когда-то JavaScript был еще молод. Было время, когда вы не могли быть уверены, будет ли данный браузер корректно выполнять JavaScript или нет. Что еще хуже, вы не могли быть уверены в том, что браузер будет знать, что такое тег script. И если браузер не распознает тег, он должен сделать его строчным элементом, то есть весь JavaScripts будет показан на странице как текст!
К счастью, спецификация смогла обеспечить решение, которое позволяет интерпретировать как HTML-комментарий все то, что не поддерживает браузер:
<script>
<!-- to hide script contents from old browsers
// You would probably be pasting a ‘rollover’ script
// you got from hotscripts.net here
// end hiding contents from old browsers -->
</script>
Конечно, как и большинство вещей, XHTML сделал это гораздо хуже. XML имеет очень специфический метод скрытия контента, который может включать в себя закрывание тегов и прочее. Так родился CDATA :
<script>
//<![CDATA[
// Is this the right incantation to get this to pass
// the XHTML validator?
//]]>
</script>
Естественно, если ваш код будет валидным XHTML, это не окажет никакого влияния на его функциональность, но это невероятно важно для вашей самооценки в качестве веб-разработчика.
Браузеры также содержат полезную функцию, позволяющую пользователям, у которых не включен JavaScript, перейти на тег NoScript. NoScript оборачивает контент, который должен отображаться, только если браузер не поддерживает выполнение сценариев:
<noscript>
Please use Internet Explorer 5.5 or above.
</noscript>
<script>
exploitInternetExplorer0Day();
</script>
Если вы наблюдательны, вы поймете, что NoScript не принимает аргумент type, что делает его взаимодействие со страницами, которые используют несколько типов сценариев несколько неоднозначным. Фактическое поведение варьируется от браузера к браузеру.
Тег Script и innerHTML
И напоследок, позвольте рассказать об одной забавной особенности.Теги Script, которые динамически добавляются на страницу с помощью DOM, выполняются в браузере:
var myScript = document.createElement('script');
myScript.textContent = 'alert(\"✋\")';
document.head.appendChild(myScript);
А теги, которые динамически добавляются на страницу с помощью innerHTML, не выполняются:
document.head.innerHTML += '<script>alert(\"✋\")</script>';
Почему? Неизвестно, но это забавный ответ на вопрос, можно ли в инспекторе отобразить скрипт, который фактически не выполняется.