Как подгрузить js файл через javascript
Решения для серверной части:
- Уменьшить размер передаваемых файлов
- Использовать CDN
- Вынести статические файлы на отдельный домен или под домен, таким образом увеличить количество одновременных соединений браузера.
- Включить сжатие передаваемых файлов(gzip)
Решения для клиентской части:
- Уменьшить количество запросов.
- Кэшировать файлы на стороне клиента с помощью заголовков Expires и Etags.
- Использовать общедоступные CDN(Google CDN, Yandex CDN). Таким образом, существует вероятность, что файл с общедоступного CDN уже будет храниться в кеше браузера.
- Асинхронная загрузка подключаемых файлов.
Одним из способов оптимизации скорости загрузки сайта является асинхронная загрузка файлов, которая не блокирует отрисовку.
Скрипт асинхронной загрузки JavaScript:
Если JavaScript нужно выполнить после загрузки всей страницы, включая содержание, изображения, стилевые файлы и внешние скрипты, то к загрузчику нужно добавить отслеживания события onload.
Скрипт асинхронной загрузки JavaScript с учетом события onload
Но это единичный случай, когда требуется загрузка одного файла. Часто на практике подключается множество файлов.
Скрипт асинхронной загрузки множества подключаемых JavaScript файлов
Но в такой реализации есть минус - скрипты будут загружаться в произвольном порядке и соответсвенно выполнятся они будут произвольно во времени. Данный скрипт асинхронной загрузки идеально подходит, если выполнение JavaScript файлов не зависят один от другого и не зависит от DOM. В обратном случае его использование может привести к ошибкам на странице или непредвиденному результату выполнения. Для последовательного выполнения, но асинхронной загрузки, нужно указать async=false, тогда файлы будут скачиваться в произвольном порядке, но выполняться по очереди.
HTML 5. Асинхронная загрузка JavaScript
Стандарт HTML 5 поддерживает асинхронную загрузку JavaScript. Это можно сделать путем добавления ключевого слова async или defer. Например:
В обеих вариантах загрузка javascript будет асинхронной. Разница состоит только в начале времени выполнения скрипта.
Скрипт, который подключен с атрибутом defer выполнится не нарушая порядок выполнения по отношению к остальным скриптам и его выполнение произойдет после полной загрузки и парсинга страницы, но перед тем, как вызовется DOMContentLoaded.
Скрипт, который подключен с атрибутом async выполнится при первой возможности после полной загрузки, но при этом не ожидает окончания парсинга документа и до загрузки объекта window. Браузеры не гарантируют выполнение скриптов в том же порядке в котором они подключены.
Библиотеки для асинхронной загрузки JavaScript
RequireJS - модуль загрузки JavaScript. Оптимизирован под браузеры, но он может использоваться в других средах, таких как Node, Rhino.
Впервые, когда я попробовал динамически загрузить .js файл, я чуть не заработал нервное расстройство. Вот что я написал сначала:
Попробовал запустить – получил ошибку времени выполнения "Undetermined string constant (неопределенная строковая константа)".
Что, во имя всего святого, это могло бы значить?
Но отыскать ответ на свой вопрос мне удалось только после прочтения JavaScript: The Definitive Guide Дэвида Фланагана. В общем, содержимое тэга <script> выполняется JavaScript-интерпретатором, несмотря на то, что он содержится внутри строки. Встает вопрос, зачем они выбрали такую непостижимо странную реализацию. Можно предположить, что это для скорости, но хорошо бы все-таки располагать фактами. Как бы я ни любил Фланагана, я был немного разочарован отсутствием разъяснения в его книге. Беглый взгляд на ECMAScript Language Specification также не принес результатов. Возможно, такое поведение является побочным продуктом HTML-парсера. Честно говоря, я не знаю. Я буду продолжать изыскания в свободное время (которое, к сожалению, имеется у меня в достаточно ограниченном количестве) и оставлю комментарий здесь, когда (если) найду. Я с радостью воздам вам почести, если вы раскроете эту ускользающую загадку вселенной.
Хорошо, но как же все-таки динамически загрузить внешний JavaScript-файл?
Я назову вам два способа решения этой проблемы. Первый из них (назовем его статическим) JavaScript-эксперты рекомендовали обычным смертным еще с тех незапамятных времен, когда наши предки впервые спустились с деревьев и, вооруженные дубинами, отправились искать себе пропитание.
Другой способ – динамический (DHTML) – по «удивительному» стечению обстоятельств использует DHTML. Насколько я знаю, я – первый человек на этой планете и даже, наверное, во вселенной (но, вероятно, не в измерении 1217.32 вселенной z540a – эти ребята действительно вяжут шпалы в узлы), кто использует этот подход. Неплохо для безработного, а? Жаль, что Межгалактическое Транспортное Управление не было заинтересованно в приглашение меня на работу в Отдел Пространственно-Временных Ретикуляций.
Итак, статический способ
Используя его, вы ставите экранирующий символ (escape character) перед закрывающим </script> тэгом следующим образом: <\/script> (что и сделано в нижеуказанном фрагменте кода). Экранирование предотвращает нежелательное выполнение JavaScript-строки, содержащей тэг <script>.
Метод staticLoadScript() в этом примере должен быть вызван, пока страница загружается (т.к. он использует document.write(), чтобы создать <script>-тэг для импорта внешнего скрипта). Если вы вызовете staticLoadScript() после загрузки страницы, это приведет вашу страницу в негодность, т.к. write() затрет содержимое документа.
Динамический (DHTML) способ
В данном случае вы создаете новый DOM-элемент «script» и добавляете его к документу.
В отличие от staticLoadScript() из предыдущего примера, метод dhtmlLoadScript() может быть вызван в любой момент выполнения программы, правда, документ должен быть хотя бы частично загружен в браузер. В указанном выше фрагменте, документ гарантированно будет загружен к моменту вызова dhtmlLoadScript(), т.к. вызов происходит в обработчике onload.
Ограничения
Приведенный выше метод dhtmlLoadScript() будет работать только в браузерах, поддерживающих динамическое добавление элементов к странице, используя DOM (Document Object Model). А значит, этот метод не заработает в Netscape версии ниже 6, а также Opera 7 и ниже. Я не считаю, это реальной проблемой, врядли кто-либо в здравом уме будет поддерживать эти давно устаревшие браузеры, по крайней мере, если на это нет чертовски хорошей причины.
Не так давно я наткнулся на данный пост о загрузке JavaScript без блокировки путем создания динамического < script > тега. Когда < script > теги находятся в потоке HTML-документа, браузер должен прекратить рендеринг и дождаться загрузки и выполнения файла сценария, прежде чем продолжить ( пример ). Создание нового < script > тега с помощью JavaScript позволяет избежать этой проблемы, поскольку он выходит за рамки документа, поэтому файл сценария загружается и выполняется без ожидания. Результат: динамическая загрузка файлов JavaScript позволяет вашей странице отображаться быстрее и, следовательно, повышает воспринимаемую производительность.
Лучшая техника
Подумав об этом и поэкспериментировав, я пришел к выводу, что есть только один лучший способ загрузки JavaScript без блокировки:
- Создайте два файла JavaScript. Первый содержит только код, необходимый для динамической загрузки JavaScript, второй содержит все остальное, что необходимо для начального уровня интерактивности на странице.
- Включите первый файл JavaScript с < script > тегом внизу страницы, прямо внутри < /body > .
- Создайте второй < script > тег, который вызывает функцию для загрузки второго файла JavaScript и содержит любой дополнительный код инициализации.
Это оно! Там действительно нет необходимости делать что-либо еще. Главное, чтобы у вас было только два JavaScript и сделайте первый как можно меньше. Например, первый файл .js может просто содержать эту функцию:
Это небольшой объем кода для загрузки, так что он будет загружаться невероятно быстро (особенно при сжатии).
Фактический код на вашей странице в конечном итоге выглядит так:
Размещение скрипта
Вы заметите, что я упомянул лучшую практику размещения этого кода в конце страницы, прямо внутри закрывающего < /body > тега. Это совет, который существует уже некоторое время, и я все еще рекомендую его, даже при использовании этой техники. Причина в том, что вам гарантировано, что все элементы DOM, которые вам могут понадобиться, уже присутствуют на странице. Загрузка ваших сценариев ранее может привести к проблемам с синхронизацией, когда вам нужно будет беспокоиться об использовании window .onload или о каком-либо другом методе, чтобы определить, когда DOM готов к использованию. Включив этот код внизу страницы, вы можете быть уверены, что DOM готов к работе, и вам больше не придется откладывать инициализацию.
Подставка первого скрипта
Несколько комментаторов правильно указали, что эту технику можно дополнительно оптимизировать, переместив исходную функцию в строку вместо сохранения ее во внешнем файле. Как правило, мне нравится хранить JavaScript вне кода страницы для удобства сопровождения. Я также ожидал, что исходный код JavaScript на странице будет больше, чем просто эта функция по той или иной причине. Если у вас может быть какая-то автоматизация для внедрения этого на вашу страницу в виде встроенного сценария, я полностью за это! Ключевой момент – убедидесь, что первый встроенный скрипт достаточно мал, чтобы производительность во время выполнения не влияла на загрузку страниц.
Пример на YUI 3
YUI 3 разработан вокруг этой самой предпосылки. Вы можете начать с загрузки файла yui.js, а затем использовать встроенный компонент Loader для динамической загрузки остальной части библиотеки YUI. Например:
Этот код сначала загружается в исходный файл YUI, затем создает новый экземпляр YUI объекта и указывает, что компонент «узел» необходим. За кулисами YUI создает URL со всеми зависимостями для «узла», динамически загружает его, а затем вызывает функцию обратного вызова после завершения. Крутая вещь в подходе YUI 3 заключается в том, что вам не нужно беспокоиться о статическом включении URL для JavaScript, просто укажите, какие компоненты вам нужны, и библиотека определит правильный URL для загрузки ( подробности ).
Вывод
Хотя было много исследований о способах загрузки JavaScript без блокировки, на самом деле есть только один способ, который я бы рекомендовал в качестве лучшей практики. На самом деле не нужно загружать ничего, кроме двух сценариев, чтобы ваш сайт был инициализирован и интерактивен. Сделайте исходный файл JavaScript настолько маленьким, насколько это возможно, а затем динамически загрузите более крупный файл, чтобы избежать блокировки рендеринга страницы. Это самый простой и легкий способ вывести весь JavaScript-код на страницу, не влияя на пользовательский интерфейс.
Введение
В этой статье я хочу научить вас как загружать в браузер JavaScript и выполнять его.
Нет, подождите, вернитесь! Я знаю, что это звучит заурядно и просто, но помните, что это происходит в браузере, где теоретически простое превращается в набор причуд, определенных наследственностью. Знание этих причуд поможет вам выбрать самый быстрый, наименее разрушительный способ загрузки скриптов. Если вы спешите, то можете перейти сразу к краткому справочнику в конце статьи.
Для затравки, вот как спецификация определяет различные способы загрузки и выполнения скриптов:
Как и все спецификации WHATWG, на первый взгляд данная спецификация выглядит как последствия кассетной бомбы на фабрике Scrabble. Но, прочитав ее на 5 раз и вытерев кровь из своих глаз, начинаешь находить ее довольно интересной:
Мое первое подключение скрипта
Ах, блаженная простота. В данном случае браузер скачает оба скрипта параллельно и выполнит их как можно скорее, сохраняя заданный порядок. «2.js» не будет выполняться пока не выполнится «1.js» (или не сможет этого сделать), «1.js» не выполнится пока не выполнится предыдущий скрипт или стиль, и т.д. и т.п.
К сожалению, браузеры блокируют дальнейшую отрисовку страницы, пока это все происходит. Еще со времен «первого века веба» это обусловлено DOM API, который позволяет строкам добавляться к содержимому, которое пережовывает парсер, например с помощью document.write . Более современные браузеры продолжат сканировать и парсить документ в фоновом режиме и загружать нужный сторонний контент (js, картинки, css и т.д.), но отрисовка по-прежнему будет блокирована.
Вот почему гуру и специалисты производительности советуют размещать элементы script в конце документа, потому что это блокирует меньше всего контента. К сожалению, это означает, что ваш скрипт не будет увиден браузером до того времени, как будет скачен весь HTML, а также уже запущена загрузка CSS, картинок и iframe-ов. Современные браузеры достаточно умны, чтобы отдавать приоритет JavaScript над визуальной частью, но мы можем сделать лучше.
Спасибо, IE! (нет, я это без сарказма)
В Microsoft обнаружили эти проблемы с производительностью и ввели «defer» в Internet Explorer 4. В основном, оно говорит следующее: «Я обещаю ничего не вставлять в парсер, используя штуки, наподобие document.write . Если я нарушу это обещание, вы можете меня наказать любым приемлемым вам способом». Этот атрибут был введен в HTML4 и он также появился в других браузерах.
В примере выше, браузер параллельно скачает оба скрипта и выполнит их прямо перед тем, как вызовется DOMContentLoaded , порядок сохранится.
Как и кассетная бомба на фабрике овец, «defer» стал мохнатым беспорядком. Помимо «src» и «defer», а также тегов скрипт и динамически загружаемых скриптов, у нас есть 6 паттернов добавления скрипта. Естественно, что браузеры не договорились о порядке, в котором они должны выполняться. Mozilla замечательно описала эту проблему еще в 2009.
WHATWG сделали это поведение явным, объявив, что «defer» не будет иметь никакого эффекта на скрипты, которые были добавлены динамически или не имеют «src». В противном случае, скрипты с «defer» должны запускаться в заданном порядке после того, как документ был распарсен.
Спасибо, IE! (ну ладно, теперь с сарказмом)
Одно дали — другое отобрали. К сожалению, есть неприятный баг в IE4-9, который может спровоцировать выполнение скриптов в неверном порядке. Вот что происходит:
Допустим, что на странице есть параграф, ожидаемый порядок логов — [1, 2, 3], но в IE9 и ниже результат будет [1, 3, 2]. Некоторые операции DOM вынуждают IE приостановить выполнение текущего скрипта и перед продолжением начать выполнение других скриптов в очереди.
Тем не менее, даже в реализациях без бага, таких как IE10 и других браузерах, выполнение скрипта будет отложено до момента, когда весь документ будет загружен и распарсен. Это удобно, если вы в любом случае ждете DOMContentLoaded , но если вы хотите получить реальный прирост производительности, то вскоре вы начнете использовать listeners и bootstrapping…
HTML5 спешит на помощь
HTML5 дал нам новый атрибут «async», который предполагает, что вы также не используете document.write, но при этом не ожидает окончания парсинга документа. Браузер параллельно скачает оба скрипта и выполнит их как можно скорее.
К сожалению, так как они постараются выполниться максимально скоро, «2.js» может выполниться раньше «1.js». Это отлично, если они не зависят друг от друга. Например, если «1.js» — это отслеживающий скрипт, не имеющий ничего общего с «2.js». Но если «1.js» — это CDN-копия jQuery, от которой зависит «2.js», то ваша страница будет устлана ошибками, как после кассетной бомбы в… я не знаю… здесь я ничего не придумал.
Я знаю, нам нужна JavaScript-библиотека!
В Святом Граале содержится набор скриптов, загружающихся сразу, без блокирования отрисовки страницы и выполняющихся максимально скоро, в том порядке, в котором мы их добавили. К сожалению, HTML ненавидит вас и не позволит вам этого сделать.
Проблема была решена с помощью JavaScript на разный манер. Некоторые способы требовали от вас вносить изменения в JavaScript, оборачивать всё в callback, который библиотека вызовет в правильном порядке (например, RequireJS). Другие использовали XHR для параллельной загрузки, а затем eval() в нужном порядке, который не работает для скриптов на другом домене, если там нет заголовка CORS и поддержки его в браузере. Некоторые использовали даже супер-магические хаки, как это было сделано в последнем LabJS.
Хаки всяческим образом обманывали браузер, чтобы тот загружал ресурс, вызывая при этом событие по окончанию загрузки, но не начинал его выполнение. В LabJS скрипт сначала добавлялся с некорректным mime-типом, например
Скрипты, которые созданы и добавлены динамически, асинхронные по-умолчанию, они не блокируют отрисовку и выполняются сразу после загрузки, что означает, что они могут появиться в неверном порядке. Однако мы можем явно пометить их неасинхронными:
Это даст нашим скриптам сочетание с поведением, которое не может быть достигнуто чистым HTML. Явно заданные неасинхронными, скрипты добавляются в очередь на выполнение, такую же, как они попадали в нашем первом примере на чистом HTML. Однако, создаваемые динамически, они будут выполняться вне парсинга документа, что не будет блокировать отрисовку, пока они будут загружаться (не путайте неасинхронную загрузку скрипта с синхронным XHR, что никогда не является хорошей вещью).
Скрипт выше должен быть встроен в head страниц, начиная очередь загрузок как можно раньше, без нарушения постепенной отрисовки, и начиная выполнять как можно раньше, в заданном вами порядке. "2.js" может свободно загружаться до "1.js", но он не будет выполнен до тех пор, пока "1.js" успешно не скачается и выполнится или не сможет сделать что-либо из этого. Ура! Асинхронная загрузка, но выполнение по порядку!
Загрузка скриптов этим методом поддерживается везде, где поддерживается атрибут async, за исключением Safari 5.0 (на 5.1 все хорошо). Кроме того все версии Firefox и Opera, которые не поддерживают атрибут async, все равно выполняют динамически-добавленные скрипты в правильном порядке.
Это самый быстрый способ загружать скрипты, так? Так?
Ну если вы динамически решаете какие скрипты загружать — да, иначе — возможно, что нет. В примере выше браузер должен распарсить и загрузить скрипт, чтобы определить какие скрипты загружать. Это скрывает ваши скрипты от сканеров предзагрузки. Браузеры используют эти сканеры для обнаружения ресурсов, которые вы скорее всего посетите следующими и для обнаружения ресурсов страницы пока парсер заблокирован другим ресурсом.
Мы можем добавить обнаружаемость обратно, поместив это в head документа:
Это сообщает браузеру о том, что страница требует 1.js и 2.js и видима для предзагрузчиков. link[rel=subresource] похож на link[rel=prefetch] , но с другой семантикой. К сожалению, это поддерживается только в Chrome, и вам нужно объявлять скрипты для загрузки дважды: первый — в элементах link, второй — в вашем скрипте.
Эта статья меня удручает
Ситуация удручающая и вы должны чувствовать себя удрученным. Еще нет декларативного способа без повторений для загрузки скриптов быстро и асинхронно, в то же время управляя порядком выполнения.
Каждый enhancement-скрипт имеет дело с конкретным компонентом страницы, но требует вспомогательные функции в dependencies.js. В идеале, мы хотим загрузить все асинхронно, затем выполнить enhancement-скрипт как можно раньше, в любом порядке, но после dependencies.js. Это прогрессивное прогрессивное улучшение!
К сожалению, нет декларативного способа для того, чтобы достичь этого, только если модифицировать сами скрипты для отслеживания состояния загрузки dependencies.js. Даже async=false не решит эту проблему, потому что выполнение enhancement-10.js будет заблокировано на 1-9. По факту, есть только один браузер, в котором можно достичь этого без хаков…
У IE есть идея!
IE грузит скрипты не так, как другие браузеры.
IE начинает закачивать "whatever.js" сейчас, другие же браузеры не начнут загрузку до того момента, пока скрипт не будет добавлен к документу. У IE также есть событие "readystatechange" и свойство "readystate", которые сообщают о процессе загрузки. Это на самом деле очень полезно, потому что позволяет нам управлять загрузкой и выполнением скриптов независимо друг от друга.
Мы можем строить сложные модели зависимости, выбирая когда добавлять скрипты в документ. IE поддерживает такую модель, начиная с 6-ой версии. Довольно интересно, но у этого есть такой же недостаток с обнаруживаемостью браузером, как и у async=false .
Хватит! Как я должен загружать скрипты?
Ладно, ладно. Если вы хотите загружать скрипты способом, который не блокирует отрисовку, не требует дублирования и имеет прекрасную поддержку браузеров, то я советую вот этот:
Именно этот. В конце элемента body. Да, быть веб-разработчиком — это как быть царем Сизифом (бум! 100 хипстерских очков за упоминание греческой мифологии!). Ограничения HTML и браузеров не позволяют нам сделать сильно лучше.
Я надеюсь, что модули JavaScript нас спасут, предоставив декларативный неблокирующий способ загружать скрипты и иметь контроль над порядком их запуска, даже если это потребует написание скриптов в виде модулей.
Иуу, должно быть что-то получше, что мы можем использовать сейчас?
Ладно, ради бонусных очков, если вы всерьез думаете о производительности и не боитесь сложности и дублирования, то можете объединить несколько рассмотренных трюков.
Во-первых, мы добавим объявление subresource для предзагрузчиков:
Затем прямо в head документа мы загружаем наши скрипты с помощью JavaScript, используя async=false , уступая место скрипту для IE на основе readystate, который, в свою очередь, уступает место defer.
Несколько трюков, затем минификация, и вот 362 байта + URL ваших скриптов:
Стоят ли того дополнительные байты, в сравнении с простым подключением скриптов? Если вы уже используете JavaScript для условной загрузки скриптов, как это делает BBC, то вы можете также получить выигрыш от раннего запуска этих загрузок. В противном случае, скорее нет, придерживайтесь простого способа с подключением в конце body.
Уф, теперь я знаю почему раздел WHATWG по загрузке скриптов такой огромный. Мне нужно выпить.
Краткий справочник
Простые элементы script
Спецификация говорит: Скачивай вместе, выполняй по порядку после любого ожидающего CSS, блокируй отрисовку, пока не закончишь
Браузер отвечает: Да, сэр!
Defer
Спецификация говорит: Скачивай вместе, выполняй по порядку до DOMContentLoaded. Игнорируй "defer" для скриптов без "src".
IE < 10 отвечает: Возможно, что я выполню 2.js в середине выполнения 1.js. Это же весело??
Браузеры красной зоны отвечают: Я понятия не имею что такое defer, я буду загружать скрипты так, как будто этого нет.
Остальные браузеры отвечают: Хорошо, но возможно, что я не буду игнорировать "defer" для скриптов без "src"
Async
Спецификация говорит: Скачивай вместе, выполняй в любом порядке, в котором они закачиваются.
Браузеры красной зоны отвечают: Что такое "async"? Я буду скачивать скрипты так, как будто этого нет.
Остальные браузеры отвечают: Да, хорошо.
Async false
Спецификация говорит: Скачивай вместе, выполняй по порядку, когда все загрузятся.
Firefox < 3.6, Opera отвечают: Я понятия не имею что такое "async", но так случилось, что я выполняю скрипты, добавленные через JS, в порядке, в котором они были добавлены.
Safari 5.0 отвечает: Я понимаю "async", но не знаю как установить его в false через JS. Я выполню ваши скрипты тогда, когда они придут, в любом порядке.
IE < 10 отвечает: Понятия не имею об "async", но есть обходной путь с использованием "onreadystatechange".
Другие браузеры красной зоны отвечают: Я не понимаю "async", я выполню ваши скрипты тогда, когда они придут, в любом порядке.
Остальные отвечают: Я твой друг, мы сделаем это как по учебнику.
Читайте также: