Погружение в темные воды загрузки скриптов

Original author: Jake Archibald
  • Translation
  • Tutorial
image
Буквально несколько часов назад на HTML5 Rocks появилась замечательная статья о текущем положении дел, касающихся загрузки скриптов на странице. Представляю вашему вниманию ее перевод. Поправки можете присылать в личные сообщения.

Введение


В этой статье я хочу научить вас как загружать в браузер JavaScript и выполнять его.

Нет, подождите, вернитесь! Я знаю, что это звучит заурядно и просто, но помните, что это происходит в браузере, где теоретически простое превращается в набор причуд, определенных наследственностью. Знание этих причуд поможет вам выбрать самый быстрый, наименее разрушительный способ загрузки скриптов. Если вы спешите, то можете перейти сразу к краткому справочнику в конце статьи.

Для затравки, вот как спецификация определяет различные способы загрузки и выполнения скриптов:

Что написано в WHATWG о загрузке скриптов

Как и все спецификации WHATWG, на первый взгляд данная спецификация выглядит как последствия кассетной бомбы на фабрике Scrabble. Но, прочитав ее на 5 раз и вытерев кровь из своих глаз, начинаешь находить ее довольно интересной:

Мое первое подключение скрипта


<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

Ах, блаженная простота. В данном случае браузер скачает оба скрипта параллельно и выполнит их как можно скорее, сохраняя заданный порядок. «2.js» не будет выполняться пока не выполнится «1.js» (или не сможет этого сделать), «1.js» не выполнится пока не выполнится предыдущий скрипт или стиль, и т.д. и т.п.

К сожалению, браузеры блокируют дальнейшую отрисовку страницы, пока это все происходит. Еще со времен «первого века веба» это обусловлено DOM API, который позволяет строкам добавляться к содержимому, которое пережовывает парсер, например с помощью document.write. Более современные браузеры продолжат сканировать и парсить документ в фоновом режиме и загружать нужный сторонний контент (js, картинки, css и т.д.), но отрисовка по-прежнему будет блокирована.

Вот почему гуру и специалисты производительности советуют размещать элементы script в конце документа, потому что это блокирует меньше всего контента. К сожалению, это означает, что ваш скрипт не будет увиден браузером до того времени, как будет скачен весь HTML, а также уже запущена загрузка CSS, картинок и iframe-ов. Современные браузеры достаточно умны, чтобы отдавать приоритет JavaScript над визуальной частью, но мы можем сделать лучше.

Спасибо, IE! (нет, я это без сарказма)


<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

В Microsoft обнаружили эти проблемы с производительностью и ввели «defer» в Internet Explorer 4. В основном, оно говорит следующее: «Я обещаю ничего не вставлять в парсер, используя штуки, наподобие document.write. Если я нарушу это обещание, вы можете меня наказать любым приемлемым вам способом». Этот атрибут был введен в HTML4 и он также появился в других браузерах.

В примере выше, браузер параллельно скачает оба скрипта и выполнит их прямо перед тем, как вызовется DOMContentLoaded, порядок сохранится.

Как и кассетная бомба на фабрике овец, «defer» стал мохнатым беспорядком. Помимо «src» и «defer», а также тегов скрипт и динамически загружаемых скриптов, у нас есть 6 паттернов добавления скрипта. Естественно, что браузеры не договорились о порядке, в котором они должны выполняться. Mozilla замечательно описала эту проблему еще в 2009.

WHATWG сделали это поведение явным, объявив, что «defer» не будет иметь никакого эффекта на скрипты, которые были добавлены динамически или не имеют «src». В противном случае, скрипты с «defer» должны запускаться в заданном порядке после того, как документ был распарсен.

Спасибо, IE! (ну ладно, теперь с сарказмом)

Одно дали — другое отобрали. К сожалению, есть неприятный баг в IE4-9, который может спровоцировать выполнение скриптов в неверном порядке. Вот что происходит:

1.js
console.log('1');
document.getElementsByTagName('p')[0].innerHTML = 'Changing some content';
console.log('2');

2.js
console.log('3');

Допустим, что на странице есть параграф, ожидаемый порядок логов — [1, 2, 3], но в IE9 и ниже результат будет [1, 3, 2]. Некоторые операции DOM вынуждают IE приостановить выполнение текущего скрипта и перед продолжением начать выполнение других скриптов в очереди.

Тем не менее, даже в реализациях без бага, таких как IE10 и других браузерах, выполнение скрипта будет отложено до момента, когда весь документ будет загружен и распарсен. Это удобно, если вы в любом случае ждете DOMContentLoaded, но если вы хотите получить реальный прирост производительности, то вскоре вы начнете использовать listeners и bootstrapping…

HTML5 спешит на помощь


<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

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-типом, например
. Как только все скрипты были загружены, они добавлялись снова, но уже с корректным mime-типом, в надежде на то, что браузер возьмет их прямо из кэша и сразу выполнит в нужном порядке. Данный способ надеялся на удобное, но непредсказуемое поведение, и сломался, когда HTML5 объявил, что браузеры не должны поддерживать скрипты с непонятным типом.

Оба этих приема имеют проблемы с производительностью сами по себе, вам нужно дожидаться момента, когда JavaScript-код библиотеки загрузится и распарсится прежде чем начнут загружаться скрипты, загрузкой которых библиотека управляет. Кроме того, как мы собираемся загружать загрузчик скриптов? Как мы собираемся загружать скрипт, который говорит загрузчику скриптов что загружать? Кто охраняет Хранителей? Почему я голый? Это всё сложные вопросы.

DOM спешит на помощь!


На самом деле, ответ находится в спецификации HTML5, хоть он и скрыт в низу раздела о загрузке скриптов.
The async IDL attribute controls whether the element will execute asynchronously or not. If the element's "force-async" flag is set, then, on getting, the async IDL attribute must return true, and on setting, the "force-async" flag must first be unset…

Давайте переведем это на "земной язык":
[ '//other-domain.com/1.js', '2.js' ].forEach(function(src) { var script = document.createElement('script'); script.src = src; document.head.appendChild(script); });

Скрипты, которые созданы и добавлены динамически, асинхронные по-умолчанию, они не блокируют отрисовку и выполняются сразу после загрузки, что означает, что они могут появиться в неверном порядке. Однако мы можем явно пометить их неасинхронными:
[
  '//other-domain.com/1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

Это даст нашим скриптам сочетание с поведением, которое не может быть достигнуто чистым HTML. Явно заданные неасинхронными, скрипты добавляются в очередь на выполнение, такую же, как они попадали в нашем первом примере на чистом HTML. Однако, создаваемые динамически, они будут выполняться вне парсинга документа, что не будет блокировать отрисовку, пока они будут загружаться (не путайте неасинхронную загрузку скрипта с синхронным XHR, что никогда не является хорошей вещью).

Скрипт выше должен быть встроен в head страниц, начиная очередь загрузок как можно раньше, без нарушения постепенной отрисовки, и начиная выполнять как можно раньше, в заданном вами порядке. "2.js" может свободно загружаться до "1.js", но он не будет выполнен до тех пор, пока "1.js" успешно не скачается и выполнится или не сможет сделать что-либо из этого. Ура! Асинхронная загрузка, но выполнение по порядку!

Загрузка скриптов этим методом поддерживается везде, где поддерживается атрибут async, за исключением Safari 5.0 (на 5.1 все хорошо). Кроме того все версии Firefox и Opera, которые не поддерживают атрибут async, все равно выполняют динамически-добавленные скрипты в правильном порядке.

Это самый быстрый способ загружать скрипты, так? Так?


Ну если вы динамически решаете какие скрипты загружать — да, иначе — возможно, что нет. В примере выше браузер должен распарсить и загрузить скрипт, чтобы определить какие скрипты загружать. Это скрывает ваши скрипты от сканеров предзагрузки. Браузеры используют эти сканеры для обнаружения ресурсов, которые вы скорее всего посетите следующими и для обнаружения ресурсов страницы пока парсер заблокирован другим ресурсом.

Мы можем добавить обнаружаемость обратно, поместив это в head документа:
<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">

Это сообщает браузеру о том, что страница требует 1.js и 2.js и видима для предзагрузчиков. link[rel=subresource] похож на link[rel=prefetch], но с другой семантикой. К сожалению, это поддерживается только в Chrome, и вам нужно объявлять скрипты для загрузки дважды: первый — в элементах link, второй — в вашем скрипте.

Эта статья меня удручает


Ситуация удручающая и вы должны чувствовать себя удрученным. Еще нет декларативного способа без повторений для загрузки скриптов быстро и асинхронно, в то же время управляя порядком выполнения.

С появлением HTTP2/SPDY вы сможете уменьшить накладные ресурсы до точки, где доставка скриптов в маленьких самостоятельно-кэшированных файлах будет самым быстрым способом. Только представьте:
<script src="dependencies.js"></script>
<script src="enhancement-1.js"></script>
<script src="enhancement-2.js"></script>
<script src="enhancement-3.js"></script>
…
<script src="enhancement-10.js"></script>

Каждый enhancement-скрипт имеет дело с конкретным компонентом страницы, но требует вспомогательные функции в dependencies.js. В идеале, мы хотим загрузить все асинхронно, затем выполнить enhancement-скрипт как можно раньше, в любом порядке, но после dependencies.js. Это прогрессивное прогрессивное улучшение!

К сожалению, нет декларативного способа для того, чтобы достичь этого, только если модифицировать сами скрипты для отслеживания состояния загрузки dependencies.js. Даже async=false не решит эту проблему, потому что выполнение enhancement-10.js будет заблокировано на 1-9. По факту, есть только один браузер, в котором можно достичь этого без хаков…

У IE есть идея!


IE грузит скрипты не так, как другие браузеры.
var script = document.createElement('script');
script.src = 'whatever.js';

IE начинает закачивать "whatever.js" сейчас, другие же браузеры не начнут загрузку до того момента, пока скрипт не будет добавлен к документу. У IE также есть событие "readystatechange" и свойство "readystate", которые сообщают о процессе загрузки. Это на самом деле очень полезно, потому что позволяет нам управлять загрузкой и выполнением скриптов независимо друг от друга.
var script = document.createElement('script');

script.onreadystatechange = function() {
  if (script.readyState == 'loaded') {
    // Our script has download, but hasn't executed.
    // It won't execute until we do:
    document.body.appendChild(script);
  }
};

script.src = 'whatever.js';

Мы можем строить сложные модели зависимости, выбирая когда добавлять скрипты в документ. IE поддерживает такую модель, начиная с 6-ой версии. Довольно интересно, но у этого есть такой же недостаток с обнаруживаемостью браузером, как и у async=false.

Хватит! Как я должен загружать скрипты?


Ладно, ладно. Если вы хотите загружать скрипты способом, который не блокирует отрисовку, не требует дублирования и имеет прекрасную поддержку браузеров, то я советую вот этот:
<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

Именно этот. В конце элемента body. Да, быть веб-разработчиком — это как быть царем Сизифом (бум! 100 хипстерских очков за упоминание греческой мифологии!). Ограничения HTML и браузеров не позволяют нам сделать сильно лучше.

Я надеюсь, что модули JavaScript нас спасут, предоставив декларативный неблокирующий способ загружать скрипты и иметь контроль над порядком их запуска, даже если это потребует написание скриптов в виде модулей.

Иуу, должно быть что-то получше, что мы можем использовать сейчас?


Ладно, ради бонусных очков, если вы всерьез думаете о производительности и не боитесь сложности и дублирования, то можете объединить несколько рассмотренных трюков.

Во-первых, мы добавим объявление subresource для предзагрузчиков:
<link rel="subresource" href="//other-domain.com/1.js">
<link rel="subresource" href="2.js">

Затем прямо в head документа мы загружаем наши скрипты с помощью JavaScript, используя async=false, уступая место скрипту для IE на основе readystate, который, в свою очередь, уступает место defer.
var scripts = [
  '1.js',
  '2.js'
];
var src;
var script;
var pendingScripts = [];
var firstScript = document.scripts[0];

// Watch scripts load in IE
function stateChange() {
  // Execute as many scripts in order as we can
  var pendingScript;
  while (pendingScripts[0] && pendingScripts[0].readyState == 'loaded') {
    pendingScript = pendingScripts.shift();
    // avoid future loading events from this script (eg, if src changes)
    pendingScript.onreadystatechange = null;
    // can't just appendChild, old IE bug if element isn't closed
    firstScript.parentNode.insertBefore(pendingScript, firstScript);
  }
}

// loop through our script urls
while (src = scripts.shift()) {
  if ('async' in firstScript) { // modern browsers
    script = document.createElement('script');
    script.async = false;
    script.src = src;
    document.head.appendChild(script);
  }
  else if (firstScript.readyState) { // IE<10
    // create a script and add it to our todo pile
    script = document.createElement('script');
    pendingScripts.push(script);
    // listen for state changes
    script.onreadystatechange = stateChange;
    // must set src AFTER adding onreadystatechange listener
    // else we’ll miss the loaded event for cached scripts
    script.src = src;
  }
  else { // fall back to defer
    document.write('<script src="' + src + '" defer></'+'script>');
  }
}

Несколько трюков, затем минификация, и вот 362 байта + URL ваших скриптов:
!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
  "//other-domain.com/1.js",
  "2.js"
])

Стоят ли того дополнительные байты, в сравнении с простым подключением скриптов? Если вы уже используете JavaScript для условной загрузки скриптов, как это делает BBC, то вы можете также получить выигрыш от раннего запуска этих загрузок. В противном случае, скорее нет, придерживайтесь простого способа с подключением в конце body.

Уф, теперь я знаю почему раздел WHATWG по загрузке скриптов такой огромный. Мне нужно выпить.

Краткий справочник


Простые элементы script

<script src="//other-domain.com/1.js"></script>
<script src="2.js"></script>

Спецификация говорит: Скачивай вместе, выполняй по порядку после любого ожидающего CSS, блокируй отрисовку, пока не закончишь
Браузер отвечает: Да, сэр!

Defer

<script src="//other-domain.com/1.js" defer></script>
<script src="2.js" defer></script>

Спецификация говорит: Скачивай вместе, выполняй по порядку до DOMContentLoaded. Игнорируй "defer" для скриптов без "src".
IE < 10 отвечает: Возможно, что я выполню 2.js в середине выполнения 1.js. Это же весело??
Браузеры красной зоны отвечают: Я понятия не имею что такое defer, я буду загружать скрипты так, как будто этого нет.
Остальные браузеры отвечают: Хорошо, но возможно, что я не буду игнорировать "defer" для скриптов без "src"

Async

<script src="//other-domain.com/1.js" async></script>
<script src="2.js" async></script>

Спецификация говорит: Скачивай вместе, выполняй в любом порядке, в котором они закачиваются.
Браузеры красной зоны отвечают: Что такое "async"? Я буду скачивать скрипты так, как будто этого нет.
Остальные браузеры отвечают: Да, хорошо.

Async false

[
  '1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

Спецификация говорит: Скачивай вместе, выполняй по порядку, когда все загрузятся.
Firefox < 3.6, Opera отвечают: Я понятия не имею что такое "async", но так случилось, что я выполняю скрипты, добавленные через JS, в порядке, в котором они были добавлены.
Safari 5.0 отвечает: Я понимаю "async", но не знаю как установить его в false через JS. Я выполню ваши скрипты тогда, когда они придут, в любом порядке.
IE < 10 отвечает: Понятия не имею об "async", но есть обходной путь с использованием "onreadystatechange".
Другие браузеры красной зоны отвечают: Я не понимаю "async", я выполню ваши скрипты тогда, когда они придут, в любом порядке.
Остальные отвечают: Я твой друг, мы сделаем это как по учебнику.

Only registered users can participate in poll. Log in, please.

Каким способом вы подключаете скрипты?

Share post

Comments 47

    +2
    Я всегда делал модульность таким образом.
    1) Создавал отдельно каждый файл с классом, который он должен реализовать. Например, мне нужно 2 класса Foo и Bar. Я создаю Foo.js и Bar.js.
    2) Также создается файл, который объединяет и создает эти классы в одном объекте (чтобы через него было просто обратится ко всем классам). Например, App.js.
    3) В каждом из классов написан такой код:
    //Foo.js
    function Foo() {
        return this;
    }
    
    Foo.prototype = {
        print: function() {
            console.debug("Foo");
            }
    }
    //Bar.js
    function Bar() {
        return this;
    }
    
    Bar.prototype = {
        print: function() {
            console.debug("Bar");
        }
    }
    //App.js
    function App() {
        this.Foo = new Foo();
        this.Bar = new Bar();
        return this;
    }
    
    //on loaded page
    window.Application = new App();
    

    Меня этот способ полностью устраивает. На таком же принципе был написан OpenLayersTools.
    Осталось 2 нерешенных проблемы: асинхронная загрузка и замыкания. Когда я создаю класс Foo и Bar, он видим в контексте окна. Хотелось бы замыкать эти классы внутри класса приложения, чтобы отгородить плагины и мой код. Асинхронную загрузку пока можно пропускать. Сжимаю грантом все скрипты приложения и получается один файл жс (без плагинов), поэтому по поводу асинхронной загрузки пока особо не переживаю.
      0
      А как унаследовать Bar от Foo?
        0
        Всё это будет работать, пока у вас не начнутся сложные зависимости. А потом подбирать порядок файлов при склейке?
        (Пока читал статью и набирал коммент, TheShock уже задал наводящий вопрос, да).
          +1
          Каким способом вы пользуетесь? Наследование и полиформизм я тяжело понимаю в JavaScript. К его прототипам я еще не совсем привык.
          В надежде найти способ, который был бы очень похожим с вышеописанным, но позволял делать замыкания классов.
            +1
            Раньше я любил использовать AMD, require.js и прочее. Потом как-то потребность так загружать модули отпала.

            Сейчас в начале каждого JS файла есть некоторая директива (скрытая в комментариях), которая указывает зависимость на файлы, что в конечном итоге позволяет нам собрать монолитный файл (ваш вариант с App.js) с правильным порядком определения модулей, классов, нэймспэйсов и так далее (ваши Foo.js и Bar.js).
              0
              Я тоже раньше игрался AMD, но не понравилось из-за получения этих самых модулей. Когда в main.js подключаешь все модули, которые возвращаются функцией. А что делать если классов много? Вручную каждый из них обрабатывать? Поэтому я с require слез, не по душе как-то пришлось.
              А вот по поводу директивы интересно. Можете подробнее рассказать? Насколько я понял, то про более старые браузеры и их поддержку этой директивы речи быть не может?
                0
                Браузеры тут не причем — это сервер обрабатывает. Смотрите sprockets , rails asset pipeline и тп.
          –20
          Используй Chrome ;)
          +9
          К настоящему моменту большинство читателей Хабрахабра, принявших участие в опросе, просто указывают скрипты в <head> и не напрягаются (по-видимому) насчёт того, что их скачивание заблокирует отрисовку.

          И это правильно.

          Какая польза читателю от незаблокированной отрисовки страницы, если для отрисованных элементов её не скачалось ещё описание поведения — так что выходит, что нечего и думать о том, чтобы невозбранно жмякать по ним мышою или пальцем? Или весь интерактив также джаваскриптами рисовать прикажете, а в первоначальный HTML только голый текст для поисковиков поместить?

          Вопрос риторический. Трюк, изложенный Вами в блогозаписи, небезынтересен (поэтому я плюсанул перевод). Однако употреблять его я не стану, вероятно.
            +11
            Полностью согласен. для меня незаблокированная страница, но без реакции на действия хуже, чем заблокированная на лишние пару секунд
              0
              Грузить скрипты в headнеправильно.

              И вот почему.

              Неблокирующая загрузка скриптов лучше, потому что я сразу вижу, что на самом деле находится на странице. Я уже могу читать текст, смотреть картинки и делать выводы: это то что мне нужно, или можно свалить отсюда. Я экономлю свое время и это намного лучше, чем терять секунды наблюдая пустой экран.

              Особенно важно асинхронно загружать сторонние скрипты: разные виджеты для лайков и т.п. Иногда загрузка страницы вашего сайты может затянуться на десятки секунд, если какой-нибудь из сторонних ресурсов ляжет. И все благодаря разработчикам, которые указывают все скрипты в head.

              Разумеется принцип не блокирования загрузки применим не ко всем сайтам — различные насыщенные веб-приложения его придерживаться не должны. Но обычным сайтам (интернет-магазины, блоги, новостные, визитки и т.п.) — желательно, если конечно для них важно сохранить своих посетителей.
                +1
                Многие сайты в реальности подгружают актуальный контент уже дальше асинхронно, после того, как платформа загрузилась. Примеры — soundcloud, coursera. Тут вы пока скрипты не загрузятся всё равно ничего читать не сможете. Неважно, где их грузить.
                  0
                  Не вижу противоречия.

                  > Разумеется принцип не блокирования загрузки применим не ко всем сайтам
              +2
              Всё очень просто. Предположим, на вашей странице очень много JS кода, который блокирует отрисовку. Хостится всё это дело не очень близко к пользователю, да ещё и канал у него не очень, так сказать, широкий. Пользователь открывает ваше веб-приложение, с грустью смотрит на белый (?) экран и значок загрузки, вздыхает, думает: «Да а нафига мне пользоваться таким непонятно работающим сервисом? Необработанные тормоза? Лежащий сервер?», ещё раз вздыхает и с высокой долью вероятностью отказывается от пользования вашими услугами.

              Другой же вариант событий: пользователь открывает веб-сервис, где видит в меру надоедливый прелоадер (= сообщение о том, что мы работаем, да! щас будет счастье и радость), ждёт и получает необходимое ему.

              Это нам с вами — как веб (и около) разработчикам — понятно, почему может быть белая страница с прелоадером. Это нам периодически не лень открыть дебаггер и «отладить» чужое приложение. Пользователям, привыкшим пользоваться продуктами из коробки это не свойственно — им хочется знать, что всё работает. Просто работает.
                –2
                Мне кажется, пользователь с «канал у него не очень, так сказать, широкий» привык, что сайты могут «непонятно работать»
                  0
                  Не настолько неширокий же :)
                  Но ожидание загрузки 2-3-х мегабайтного JS может оказаться достаточно утомительным, если просто пялиться в белый экран. Сложно ведь не согласиться с тем, что проявить уважение к пользователю и сказать: «Чувак, мы грузимся, всё в порядке» лучше, чем проявлять бездействие.
                  0
                  И так же может получиться что скрипт подгружаеться с другого сервера (онлайн-чат, шаринги всякие), и этот сервер не отвечает (лежит, тех работы и тд). При этом ожидать 30-60 секунд с белым экраном не у каждого хватит терпения, скорее всего придут к выводу что сайт лежит. При отрисовке страницы можно будет ознакомиться с информацией на сайте за это время (и решить, полезен сайт или нет)
                    0
                    Нужно просто знать все имеющиеся инструменты и уметь ими пользоваться. Иногда нужно грузить в head, иногда в body. Бездумно размещать либо там, либо там — глупо.
                      +2
                      Может, но время загрузки скрипта — это тоже контролируемо. Вы ж, наверняка, видели, как это сделано в gmail: если за N секунд скрипт не загрузился, то показать сообщение, мол, может быть, что-то пошло не так. Это всё же лучше, чем бездействие в полной мере. А можно пойти дальше, и, если определённый файл за N секунд не загрузился, то попробовать загрузить его с другого CDN, например.

                      > При отрисовке страницы можно будет ознакомиться с информацией на сайте за это время (и решить, полезен сайт или нет)
                      Сайты, на которых можно ознакомиться с текстовой информацией, вообще не должны нести в себе кучу джаваскрипта (или накрайняк первая страница может быть отдана статическим контентом). Я всё же говорю о серьёзных веб-приложениях, где обойтись без большого объёма JS достаточно сложно.
                        0
                        Сайты, на которых можно ознакомиться с текстовой информацией, вообще не должны нести в себе кучу джаваскрипта

                        Так они обычно и не несут. К примеру пост на каком нибудь блоге, кототые так часто любят усеять разными там шаринк кнопками/виджетами — сайт по сути не несет в себе кучу скрипта, но зависит от стороннего сервера. Я довольно часто попадаю на такие сайты.
                        Я всё же говорю о серьёзных веб-приложениях, где обойтись без большого объёма JS достаточно сложно.

                        Для таких приложений скрипты расположены скорее всего на тотм же сервере, и проблема таймаутома в этом случае врят ли сильно беспокоит разработчиков

                        Ну и в комментарии выше верно сказано — обдуманно располагать скрипты
                          0
                          Для таких приложений скрипты расположены скорее всего на тотм же сервере

                          Не очень логичное, на мой взгляд, утверждение: мне всё чаще приходится сталкиваться с сервисами, у которых статический контент разнесён по CDN, причём, на несколько серверов сразу (имею ввиду, JS и CSS на одном, изображения на другом, видео на третьем и так далее — видимо, по соответствующей настройке для каждого типа/размера файлов). Но спорить здесь не буду — к сожалению, у меня нет для этого никакой статистики.

                          Что касается того же сервера, то верить, что случайно что-то не упадёт — достаточно наивно. Динмический контент отдаётся одним способом, статический — другим. Навернулся где-нибудь кэш-сервер — и началось…
                  +3
                  Статья хорошая, запутали ещё больше, спасибо!
                  Это ирония конечно, сам же использовал разные сценарии по юзер-агенту, благо движок позволял. Потом открыл компиляцию в один файл (в head) и сплю спокойно.
                  И да — чуровщина какая-то в вашем голосовании.
                    0
                    Никакой чуровщины, там можно выбрать несколько ответов одновременно.
                    +1
                    Если скриптов не много, то я сжимаю их вместе и включаю в конец страницы.
                    Такая техника используется на Google Maps и избавляет от проблем с запросами и кешированием.

                    Да, размер каждой страницы вырастает на 25% (40-50 KB), но на бизнес-критичных сервисах плюсы окупают этот минус.
                    • UFO just landed and posted this here
                        +2
                        Ненавижу, когда при загрузке страницы быстро жму ссылку, и вместо соответствующего действия открывается JSON как в твиттере. Тут, конечно, твиттер не прав: надо ставить нормальные ссылки в href и стоило бы улучшить навигацию: списки далеко спрятаны; но когда загружается новая страница, это медленнее, чем обновление страницы новой информацией скриптами.

                        Те сайты, где актуальны такие оптимизации, уже являются по сути веб-приложениями, где поведение неотделимо от самого сайта.

                        Оптимизация воспринимаемой скорости загрузки — это хорошо, но пользователь справедливо ожидает, что отображённая часть уже должна работать. И тут уже встают другие вопросы к технике оптимизации. Некоторые (включая российских гигантов) внедряют скрипт прямо в тело страницы. Или надо подумать о том, как вести себя при нажатии на элемент, особенно ссылки, на которую ещё не загружен и установлен обработчик.
                          +2
                          Странно видеть лидирующим ответ «Обычным, в head» — 61 %
                            0
                            Чем странно? Я вот для приложений, для которых важен JS всё включаю в head. Да и вообще часто оставляю body пустым, без контента.
                              0
                              Ну, для специфических приложений типа вашего это оправдано. Для контентных сайтов и т.п. нет
                            0
                            Стоит отметить, что вставка скриптов через javascript не блокирует загрузку других ресурсов, однако отодвигает время срабатывания события window.onload. Этого можно избежать, если подключать скрипты через setTimeout:
                            [
                              '//other-domain.com/1.js',
                              '2.js'
                            ].forEach(function(src) {
                              setTimeout(function() {
                                var script = document.createElement('script');
                                script.src = src;
                                script.async = false;
                                document.head.appendChild(script);
                              }, 0);
                            });
                            

                              –2
                              А почему всё так плохо с инновациями в яваскрите? В мс-досе было понятнее и прямее. Наверное не осталось ни одного настолько беспомощного языка во времена бюджетных 8миядерных процессоров. У них ТАМ тоже коррупция все деньги на разработку сжирает?
                                +8
                                Судя посту, Вы что MS-DOS, что современный JavaScirpt достаточно плохо себе представляете.
                                  0
                                  Сомневаюсь, что про мс-дос кто-то догадался бы писать статью про то, что инструкции выполняются в том порядке, в каком они сами захотят. Если наезд на меня был обоснован силою скрипта, подскажите какая в нем синхронизация, а то про нее в статье очень не хватает.
                                    +1
                                    Не знаю насчет статьи, но при создании autoexec.bat надо было учитывать зависимости «ручками» и знать «синхронную» команду запускаешь )(типа nc) или «асинхронную» (типа keyrus).

                                    И вообще к javascript порядок загрузки имеет мало отношения, это проблемы html и браузеров.
                                      0
                                      К html это тоже маловато отношения имеет, html и javascript здесь инструменты. А проблема — про браузеры и DOM.
                                        0
                                        DOM в браузере явно зависит от html. Толком управление порядком загрузки в html не предусмотрено. Ввели бы атрибут у script типа depends=%id% (над синтаксисом лень думать) и большую часть проблем поднятых в посте можно было бы решить.
                                          –1
                                          Когда я там, наверху, где заминусовали, писал про коррупцию и кризис, я имел ввиду всю веб-отрасль в целом во всеми ее консорциумами и браузерами. Врядли нормальный человек 80х годов, увидев всё что здесь происходит, похвалил бы корпорации за их здравый разум. Какая разница во что здесь упёрлось — дом или не дом, скрипт или не скрипт. Проблема в этой огромной нестабильной шизе. Доходит до того, что вебразработчикам приходится на стол ставить по 4 монитора. Неужели вы этого не видите? В турбо паскале было достаточно одного монитора, а сейчас недостаточно трех не потому, что сейчас жизнь стала слаще, а потому что технология свихнулась.
                                          Те кто понял комент, положа руку на сердце, скажите, почему так вышло?
                                0
                                Извините, но в статье чувствуется какая-то однобокость и непонимание сути проблемы.

                                Нет однозначного ответа на вопрос «как вы загружаете скрипты?».

                                Загружаемая браузером асинхронно в общем случае из нескольких источников html страница представляет собой довольно сложный объект, и при для его нормальной работы используется масса разных способов.

                                То, без чего сложно обойтись, и что хорошо кэшируется ставится в head, а дельше уже начинается полет фантазии в зависимости от решаемой задачи. Тут делают и аснихронную загрузку с рестартом обломавшихся модулей, и используют localStorage, и ранее популярный трюк с setTimeout( my_lodaer, 1).

                                В общем, здесь можно много о чем поговорить, но не в духе «куда вы ставите тэги script — в начало или в конец».
                                  –2
                                  Этот перевод меня удручает.

                                  Тем не менее, спасибо за предоставленную информацию.
                                    0
                                    Поправки можете присылать в личные сообщения :)
                                    0
                                    Я загружаю скрипты при помощи head.js.
                                      0
                                      А как же importScripts() в Воркерах?
                                        0
                                        гораздо интереснее переопределить document.write и грузить все скрипты по window.onload…
                                          0
                                          Я твой друг, мы сделаем это как по учебнику.

                                          Такая манера подачи спеки просто поднимает настроение и усваивается на порядок лучше =)
                                            0
                                            На примере Twitter Bootstrap у меня в Chrome.
                                            Если на странице, где bootstrap.js подгружается в футере страницы, активно долбить F5, то частенько видна страница без оформления. При переносе в head — просто пустая страница. Хорошо это или плохо, но тут вариант с head выглядит лучше с моей, далеко не frontend, колокольни :)
                                              0
                                              В кратком справочнике не совсем корректно написано про Defer
                                              Спецификация говорит: Скачивай вместе, выполняй по порядку до DOMContentLoaded. Игнорируй «defer» для скриптов без «src».

                                              Можно подумать, что скрипты могут выполниться до того как загрузится DOM. Мне кажется, лучше не «до DOMContentLoaded», а «прямо перед вызовом события DOMContentLoaded».
                                              А статья замечательная, спасибо за перевод.

                                              Only users with full accounts can post comments. Log in, please.