Асинхронная загрузка javascript файлов. Ускорение и оптимизация процесса, увеличение производительности

    Продолжаем цикл статей на тему построения сайтов (веб-приложений) ориентируясь на максимальное применение AJAX технологии. Зачастую, при работе сайтов и приложений использующих AHAH требуется вместе с подгружаемым HTML контентом догружать файлы скриптов, которые не использовались ранее на страницах сайта. Одним из моментов обработки догружаемого AJAX-ом контента является выделение из получаемого контента файлов скриптов, с последующим их применением к текущему документу (DOM). Благо, на данный момент практически все основные библиотеки умеют это делать. Однако, если копнуть глубже в реализацию процесса обработки догружаемых скриптов, то текущая ситуация совсем «не радужная». Не все хорошо известные библиотеки умеют это делать хорошо, а тем более оптимально. Давайте рассмотрим указанный процесс изнутри, разберем существующие проблемы и узнаем пути их решения.

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

    Итак, в стартовую страницу будем подгружать дополнительную страницу test1.html, текст которой приведен ниже
    <html>
    <head>
     <script type="text/javascript" src="test1.js"></script>
     <script type="text/javascript" src="test2.js"></script>
     <link type="text/css" rel="stylesheet" href="test.css"/>
    </head>
    <body>
     Hello Bingo!
    </body>
    </html>


    Загрузим данную страницу с помощью всем известной библиотеки jQuery. Текст стартовой страницы jquery1.html приведен ниже
    <html>
    <head>
     <script type="text/javascript" src="test1.js"></script>
     <script type="text/javascript" src="jquery-1.2.6.js"></script>
    </head>
    <body>
     <div id="content1"></div>
     <div id="content2"></div>
     <script type="text/javascript">
      $(function(){
       $('#content1').load('test1.html')
       $('#content2').load('test1.html')
      })
     </script>
    </body>
    </html>



    Для определения проблемных мест загрузим дополнительную страницу дважды в блоки content1 и content2 соответственно.

    Консоль firebug-а покажет нам примерно следующее

    GET test1.html
    GET test1.html
    GET test1.js?_=1239115065471
    GET test2.js?_=1239115067323
    GET test1.js?_=1239115068456
    GET test2.js?_=1239115069347

    Как видно, jQuery, при асинхронной дозагрузке контента, прибавляет к адресам скриптов приставку типа _=1239115067323, для того чтобы скрипт не кешировался. Таким образом, при использовании jQuery, браузер каждый раз один и тот же скрипт на разных страницах будет загружать заново, по полному циклу, не используя кеш. Спрашивается «Зачем?» Вероятность того что для разных страниц один и тот же скрипт (с одинаковым адресом) будет разный практически = 0. Соответственно, процесс дозагрузки скриптов явно продуман не до конца. Однако, цель статьи не в том чтобы раскритиковать работу jQuery. Аналогичным образом обстоят дела и с другими библиотеками. Цель статьи в том, чтобы понять принципы и пути возможной оптимизации процесса асинхронной дозагрузки скриптов.

    В данной статье, для более простого изложения и понимания материала, относительно способов и вариантов оптимизации асинхронной загрузки скриптов, будет описана реализация оптимизации рассматриваемого процесса в библиотеке Fullajax

    Оптимизация шаг 1 – Начало у причала


    Зачем загружать то, что уже есть в памяти и загружено на стартовой странице?

    Обратите внимание на то, что скрипт test1.js уже подключен на стартовой странице, т.е. он уже есть в памяти. Соответственно достаточно легко можно получить его содержимое, без какой либо дополнительной загрузки данных с сервера. Таким образом, первое, что мы делаем – это проверяем, не загружен (подключен) ли уже скрипт в страницу стандартным путем? Если да – просто получаем его содержимое и выполняем. Выполняя указанную простую операцию, относительно нашего примера, мы сразу избавляемся от 2-х лишних запросов к серверу.

    Оптимизация шаг 2 – Повторение мать мучения


    Зачем загружать повторно то, что уже однажды загружено?

    Посмотрим еще раз на консоль firebug-a. Обратите внимание на порядок и количество загрузок скриптов с помощью jQuery. Скрипт test1.js и test2.js загружаются дважды каждый! Со скриптом test1.js мы разобрались на предыдущем шаге. Теперь оптимизируем загрузку второго скрипта. Скрипт test2.js достаточно загрузить один раз (первый раз), положить его содержание «за щеку» (в программный кеш) и все последующие разы доставать его оттуда, а не из сервера. Таким образом, относительно нашего примера мы избавляемся еще от одной лишней загрузки скрипта и соответственно лишнего запроса к серверу.

    В сумме, на простом примере мы имеем «конкретную» оптимизацию, а именно в итоге всего 1 запрос из 4-х изначальных запросов. Вышерассмотренные шаги оптимизации работают в библиотеке Fullajax по умолчанию. Т.е. по умолчанию повторно не загружаются скрипты стартовой страницы, а также, все новые скрипты загружаются только один раз.

    fullajax.ru/temp/asyncjs/fullajax1.html

    Реализация оптимизации шаг №1 еще более-менее прозрачна – находим все скрипты в блоке head и получаем их содержимое. Реализация оптимизации шаг №2 требовала дополнительной синхронизации между параллельными асинхронными запросами страниц с сервера. Часто возникает ситуация когда второй параллельный запрос начался до того как закончился первый, т.е. второй запрос начался до того как мы получили текст скрипта. Соответственно, необходимо обеспечить синхронизацию между потоками, чтобы по окончанию загрузки скриптов из первого потока, об этом сразу же узнал второй поток. В общем, это более углубленная тема…

    К счастью и к удивлению на этом варианты оптимизации незакончены. Мы можем еще убрать лишние «движения».

    Оптимизация шаг 3 – Переинициализация враг оптимизации


    Зачем выполнять (инициализировать) повторно то, что уже однажды выполнено (инициализировано)?

    В предыдущих шагах оптимизации, избавившись от повторной загрузки скриптов, мы все же продолжаем повторно исполнять одни и те же скрипты. Давайте посмотрим внутрь скриптов test1.js и test2.js.

    Скрипт test1.js
    var a = Math.random();

    Скрипт test2.js
    function test(){
     alert('Hello');
    }

    Относительно скрипта test1.js – нам важно его исполнять каждый раз, при каждой новой загрузке, так как от этого будет зависеть значение переменной a. Тут мы ничего поделать не можем, смиримся с этим.

    Относительно скрипта test2.js – повторное его выполнение не несет никакой полезной нагрузки. Функция test уже однажды определена и при каждой последующей загрузке данного скрипта, эта функция просто переопределяет сама себя. Соответственно, мы можем спокойно не осуществлять повторное выполнение данного скрипта. Таким образом, мы сэкономим процессорное время и возможно память. Однако, у нас нет подручных стандартных средств, которые позволяют выполнять скрипт только однажды (привет стандарту HTML). Для этого необходимо либо изменять внутреннюю структуру скрипта, так чтобы проводилась проверка на предыдущее выполнение, либо наделить движок (библиотеку) асинхронной загрузки скриптов соответствующим алгоритмом.
    Раcсмотрим реализацию данного шага оптимизации в Fullajax. Для того чтобы осуществлять одноразовое выполнение некоторых скриптов необходимо и достаточно всего лишь добавить им «волшебный» атрибут ax:repeat='0'.
    <script type="text/javascript" ax:repeat="0" src="test2.js"></script>
    И все :). Далее алгоритм загрузки скриптов автоматически будет оптимизировать выполнение таких помеченных скриптов, т.е. такие скрипты будут выполняться только однажды. Определение необходимости или отсутствия необходимости повторного выполнения (инициализации) скриптов делает сам разработчик. Проанализируйте ваши скрипты! Если нет необходимости их повторного выполнения – смело помечайте их соответствующим атрибутом. В некоторых случаях, повторное выполнение отдельных скриптов даже вызывает ошибки в работе сайта (веб-приложения). Таким примером является многим известный скрипт календаря (Zapatec Calendar). Если повторно, при AJAX подгрузке контента, выполнять скрипт calendar.js из набора скриптов, календарь перестает работать и выдает ошибки.

    Не устали? Я немного уже «запарился» описывать… представьте сколько вышеописанное программировалось :).

    И это еще не все возможные пути оптимизации, есть еще, что можно прооптимизировать при асинхронной загрузке скриптов.

    Оптимизация шаг 4 – параллели песни пели


    Давайте рассмотрим случай, когда в подгружаемой странице имеется несколько скриптов. К примеру
    <html>
    <head>
     <script type="text/javascript" src="test1.js?1"></script>
     <script type="text/javascript" src="test1.js?2"></script>
     <script type="text/javascript" src="test1.js?3"></script>
     <script type="text/javascript" src="test1.js?4"></script>
     <script type="text/javascript" src="test1.js?5"></script>
     <script type="text/javascript" src="test1.js?6"></script>
     <script type="text/javascript" src="test1.js?7"></script>
     <script type="text/javascript" src="test1.js?8"></script>
     <script type="text/javascript" src="test1.js?9"></script>
     <script type="text/javascript" src="test1.js?10"></script>
    </head>
    <body>
     Hello Bingo!
    </body>
    </html>

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

    fullajax.ru/temp/asyncjs/jquery2.html

    И это вполне нормально! Точно так же ведут себя все браузеры при стандартной загрузке скриптов. Потому что выполнение последующего скрипта может зависеть от предыдущего. Поэтому надо дождаться выполнение первого, а затем выполнять второй.
    Однако! «Выполнять» совсем не равносильно «загружать». Мы ведь можем осуществлять параллельную загрузку всех скриптов?.. А вот выполнять их будем уже последовательно, по мере их загрузки.
    Таким образом, реализовывая вышеуказанный алгоритм, мы получим настоящую безопасную асихронную загрузку скриптов. Данный алгоритм оптимизации так и назван – алгоритм параллельной загрузки с последовательным применением.

    fullajax.ru/temp/asyncjs/fullajax2.html

    При более приближенных к реальным условиям, загружаем большие файлы скриптов.

    jQuery

    fullajax.ru/temp/asyncjs/jquery3.html

    Fullajax

    fullajax.ru/temp/asyncjs/fullajax3.html

    Выводы очевидны.

    Указанный алгоритм оптимизации загрузки скриптов реализован в библиотеке Fullajax и используется по умолчанию.

    И еще немножко оптимизации – пропускать ~ чудеса исполнять


    Иногда, при подгрузке контента с помощью AJAX, возникает необходимость отключить выполнение тех или иных скриптов, и при этом же осуществлять их нормальное выполнение при обычной стандартной загрузке. Простого стандартного способа для этого не существует. Чтобы реализовать указанную функциональность приходится на сервере определять каким образом запрашивается контент (через AJAX или стандартным путем), в соответствии с этим, добавлять или отнимать из контента страницы определенные скрипты. При использовании Fullajax существует способ намного проще – добавьте «чудесный» атрибут к стилю ax:skip='1'. Теперь библиотека будет пропускать (игнорировать) помеченные вами соответствующим образом скрипты, даже не будет пытаться загружать их.

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

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

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

    jQuery я тоже люблю :).

    UPD: — добавлены тесты при загрузке больших файлов скриптов.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 39

      +3
      спасибо, хочу еще =)
        +2
        а если попробовать слепить все js в один файл?
          0
          попробуйте, тогда возникнут другие проблемы :)… в данной статье рассматриваются возможные узкие места. в реальности они могут быть а могут и не быть, в зависимости от ситуации…
          –1
          Отличная статья, насрал Вам плюсом в карму :)
            –1
            И я тоже поднасрал!
            Спасибо за статью, непременно поковыряю в ближайшее время!
            Чертовски приятно, что наши соотечественники делают вещи наравне с буржуями.
            Надеюсь, что скоро тенденция искать все новинки и плюшки сначала за бугром изменится.
              0
              Засранцы :)
              Нет чтоб освятить карму, так они туда это…
            +1
            Не могли бы вы поподробнее рассказать о реализации алгоритма?
              0
              алгоритма параллельной загрузки с последовательным применением? — исходя из названия грузим все сразу, и по мере загрузки скриптов, выполняем их последовательно. если раньше загрузился более дальний по очередности скрипт, он не выполнятся, а ждет пока все предшествующие ему загрузятся и выполнятся…
                0
                На ClientSide 2007 был доклад по модульности в JS.

                Здесь есть элегантный пример последовательного вызова загруженных JS файлов.
                jsx.ru/Texts/ModulesInJS/index.html
                0
                О, тоесть можно одним js-ником подгружать другие js?
                  0
                  одним ccs-ом, можно подгружать другие css. одним html-ом можно подгружать другие html. продолжите сами…
                    +2
                    а одним js можно подгрузить и html, и css, и все что угодно :)
                      0
                      ответ верный :)
                  0
                  стилистика статьи — просто отстой.
                  Такой концентрации канцелярщины и мусорных оборотов еще поискать надо.

                  Пишите проще, к вам потянутся люди.
                    +1
                    Вопрос на засыпку: почему при использовании fullajax — каждый скрипт (22байта) грузится(выполняется?) в 1.5-3 раза дольше чем при использовании jquery? 80-126ms против 107-255?
                    И как себя ведут не тестовые скрипты в 22байта, а нормального размера скрипт в Nкб ( хотя бы 7-15)?

                    22байта — абсолютно не серьезно и на «рабочих» скриптах результаты могут быть совсем другие. Не могли бы вы протестировать библиотеки в более приближенных к жизни обстоятельствах?
                      0
                      тоже хороший вопрос. в 1.5. раза дольше ответ от сервера, потому что к нему несколько параллельных запросов одновременно. Вот и думает немного дольше. при большом наплыве народу время отклика примерно одинаковое.

                      При более приближенных к жизни обстоятельствах ситуация еще плачевнее. В статью добавил картинки и ссылки на дополнительные тесты. Выводы делайте сами.
                        0
                        А кипалив не работает в этом случае? Все было бы гораздо быстрее с ним…
                      +1
                      А почему 1.2.6? Вероятно, 1.3.2 может себя вести иначе.
                        0
                        ждал вопроса :), 1.3.2 ведет себя аналогично.
                          0
                          Напиши Рисигу, вероятно, есть причины такого поведения. Это правда интересно.
                            0
                            кстати, и правда, собрался писать ему :)… причин может быть несколько, я думаю одними из них есть — сложность реализации и размер библиотеки.
                        0
                        > Как видно, jQuery, при асинхронной дозагрузке контента, прибавляет к адресам скриптов приставку типа _=1239115067323, для того чтобы скрипт не кешировался.

                        Use cache=true, Luke. docs.jquery.com/Ajax/jQuery.ajax#options
                          0
                          ну и? какие ваши соображения относительно этого момента?
                            0
                            Просто традиционно отмечаю, что jQuery умеет несколько больше гитик, чем Вы думаете…

                            В целом же материал вполне интересный.
                              0
                              да я и до этого знал, что jQuery имеет кеш. Помимо документации, я и исходники анализирую. Относительно этого пункта оптимизации — вопрос следующий: «Зачем включен по умолчанию антикеш для скриптов?»
                                0
                                Знали, а написали, что не умеет. Нехорошо, мелко.

                                А написать метод $.getCachedscript или даже перекрыть $.getscript — дело пяти строчек кода…
                                  0
                                  где написал что не умеет? тут?

                                  > Соответственно, процесс дозагрузки скриптов явно продуман не до конца. Однако, цель статьи не в том чтобы раскритиковать работу jQuery.
                                  0
                                  Потому что обычно там данные, а как раз данные не нужно кэшировать. Долой кривые настройки корпоративных прокси.
                                    0
                                    насчет корпоративных прокси трудно что-то сказать… наверно это дело имеет место… думаю об этом должны заботится разработчики сайтов, путем изменения имени скрипта (к примеру версия в имени) при изменении его содержимого
                                      0
                                      За самими скриптами — следят, похожим образом forms.js?ver=1234A
                                      А вот за данными на страницах — так не последишь.
                                        0
                                        да именно так, в данной статье рассмотрена оптимизация запроса скриптов, оптимизация запроса данных — это отдельный вопрос.
                            0
                            Вообщем, у библиотеки есть две проблемы:

                            1) применимость строго в рамках домена страницы (потому как XHR aka AJAX) — соответственно ценность асинхронной загрузки JS снижается, поскольку все используемые JS можно упаковать в один файл, пройтись yuicompressor и включить gzip на сервере. Точка.

                            2) руки авторов. Глянул в код — первые 100 строчек хорошо, а дальше — тихий ужас. Использовать такое в коммерческих проектах — большой риск. Чувствуется опыт программирования в PHP и отсутствие опыта программирования в JS.

                            А вцелом, продолжайте. Может что-то и вырастет из этого начинания ;-)
                              0
                              1) похоже у вас небольшой опыт в разработке сложных комплексных решений одностраничных веб приложений и простых сайтов с использованием AHAH. Упаковка в один файл и gzip никак не противоречат асинхронной загрузке, а наоборот ее улучшают как и в простом случае.
                              2) вы ошиблись с опредедлением опыта. если вам что-то не понятно в коде, это не значит что это неправильно. код всегда можно улучшать, предела совершенству нет, однако есть предел эффективности.

                              продолжаем и уже давно выросло.

                              вы не поняли цель статьи — что даже jQuery можно сделать лучше, главное головой думать и осознавать что происходит, повторюсь — предела нет совершенству.
                                0
                                > Упаковка в один файл и gzip никак не противоречат асинхронной загрузке, а наоборот ее улучшают как и в простом случае.

                                То есть вы хотите сказать, что в браузере загрузка N файлов в M потоков в рамках одного домена выгоднее загрузки 1 файла в один поток? Вы действительно так думаете?

                                > если вам что-то не понятно в коде, это не значит что это неправильно.

                                Вас публично ткнуть в ваш же код, чтобы вам стыдно стало?

                                > вы не поняли цель статьи

                                Цель статьи, как и проекта лежит на ладони — попытка пиара на теме сомнительной ценности с ужасным качеством реализации. Надо быть слепым чтобы не увидеть messages вашего сайта: «Смотрите, какие мы молодцы! Мы профи! Мы уникальны! Купите нас скорее!»

                                  0
                                  а вы и в правду думаете что все упаковывают в один файл? или что вы всегда можете это сделать? или что это во всех случаях оптимальное решение?

                                  по поводу кода буду рад если «ткнете», можно и публично, исходный код для этого и открыт, а также создан форум.

                                  не хочу вас обидеть, но «в глазки наверно балуетесь». где вы на сайте увидели «купите нас»? мы не продаемся.

                                  а цель статьи достигнута, если студенты пишут дипломные работы на базе таких статей (а таковые прецеденты есть) — значит я не зря старался.
                                    0
                                    ну что? как там по поводу «ткнуть»?
                                      +2
                                      Ну если так хочется, ловите. Disclaimer: многа букаф ;-)
                                      =======================================

                                      Открываем fullajax.ru/temp/asyncjs/fullajax1.html и лезем в Firebug. Оппа! У window появился ряд новых свойств, в том числе:
                                      SRAX (ну это понятно, точка входа),
                                      a (это что?),
                                      abort, abortData, arrayIndexOf, arrayRemoveOf, dax, error, get, getData, getInterface, go, hax, id, info, log, post, postData, warn.

                                      Кроме этого, Firebug ябедничает, что некоторые функции не всегда возвращают значение, есть ряд undeclared variables и ряд undefined property.

                                      Теперь заглянем в код. Качаем архив с сырцами и открываем fullajax.js.
                                      Таакс… Хорошо… Хорошо… оппа, модификация String.prototype — кто разрешил?

                                      String.prototype.trim() = ресурсоемкое решение.

                                      String.prototype.replaceAll() = элегантно!

                                      String.prototype.endWith() = борщ! + можно сделать эффективнее.

                                      hax():
                                      if (options.nohistory == null) options.nohistory = options.noHistory; // это типа для обратной совместимости? ;-)

                                      get() — в условии if — ошибка, можно написать это с одним return.

                                      post() — копипаст с get().

                                      dax() — копипаст с hax().

                                      SRAX.extend():
                                      var overwrite = !skipexist; // прикольно звучит, да?

                                      ===================
                                      SRAX.init():
                                      — var n = 'addEventsListener';

                                      $[n]($.HTMLThread);
                                      $[n]($.History);
                                      $[n]($.DATAThread);

                                      // конструкция интересная, но наводит на мысль о незнании или непонимании автором концепции замыканий.

                                      document._write = document.write;
                                      document._writeln = document.writeln;

                                      // пфф… опять таки, кто разрешил лезть в document?

                                      ===================

                                      $.Default.initCPLNLS() = копипаст с $.Default.initCPLNLL()

                                      IE_XHR_ENGINE: ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP'] = зачем это в таком явном виде? Завтра ожидается новый XMLHTTP от MS?

                                      delHost() = лишний if, лишнее присваивание

                                      paintHtml(), paintHtml2() == когда будет paintHtml3()?

                                      ===================
                                      DATAThread(), HTMLThread()
                                      — 1. Копипаст в 200+ строчек!!!

                                      2. _this = this. // Так и есть, автор не умеет пользоваться замыканиями. А все потому что Java.

                                      3. борщ в коде, например:

                                      if (!options.url && options.src) options.url = options.src;
                                      if (!options.cb && options.callback) options.cb = options.callback;
                                      if (options.cbo == null && options.callbackOps != null) options.cbo = options.callbackOps;
                                      if (options.anticache == null && options.nocache != null) options.anticache = options.nocache;
                                      if (overwrite) ops = {};
                                      $.extend(ops, options);
                                      if (ops.async == null) ops.async = true;
                                      ops.url = $.delHost(ops.url);
                                      this.options = ops;
                                      return _this;

                                      ===================

                                      Дальше смотреть не стал — зрение дороже. Код абсолютно нечитабелен, приватные по смыслу свойства и методы торчат снаружи SRAX и даже в window — кто не спрятался, я не виноват!

                                      Резюме: курсовая работа студента 3-го курса. Выполнена на крепкую троечку, не более того. ;-)

                                        +1
                                        ну спасибо,

                                        по делу замечено
                                        0) trim — ресурсоемок
                                        1) get() — можно записать с одним return
                                        2) копипаст DATAThread(), HTMLThread() — это давно мечтаю сделать через один объект, и таки сделаю
                                        3) код не читабелен, да есть такое дело, ну это наложение многих причин, в том числе и того что я хорошо читаю обфусфицированый код, поэтому наверно пишу сокращенно и иногда логически трудно понимаемо

                                        некоторые пояснения простых вещей
                                        1) конструкции типа
                                        var n = 'addEventsListener'; $[n]($.HTMLThread);
                                        используются для оптимизации размера кода, это в результате меньше чем использовать прямое название функций, выигрыш не очень велик но существует.
                                        2) document._write = document.write; сохранение функциональности исходной функции document.write, так как дальше будет переопределение таковой.

                                        — уверен вам трудно многое понять что для чего и почему, в силу того что никогда не сталкивались и врятли столкнетесь с проблемами и ситуациями которые встречаются при AHAH.

                                        критических замечаний нет, и это радует

                                        спасибо за уделенное время, я учту ваши некоторые замечания
                                          0
                                          да еще $.Default.initCPLNLS() = копипаст с $.Default.initCPLNLL() тоже в принципе по делу,
                                          уже вынес общее :)

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

                                Самое читаемое