Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Шаблоны проектирования Ajax. Выпуск 3. JavaScript-по-запросу.


Ещё раз добрый вечер, уважаемые подписчики!

После очередного полугодичного отпуска, рассылка снова на ходу, за это время в рунете появилось множество современных приложений, а добавление комментариев без перезагрузки страницы, похоже становится хорошим тоном, вспомним news2.ru или В контакте.

Думаю будет уместно представить и собственный проект GuidoScope, где я и мои друзья применили на практике знания подчерпнутые из переводов. В частности фолксономию на основе библиотеки FreeTag и эффекты из script.aculo.us

Итак JavaScript применяется всё чаще, библиотек на страницу вешается всё больше, как же не дать себя завалить лишним кодом? Одно из решений проблемы - подгружать на страницу только то, что необходимо, об этом и будет сегодняшний перевод. Если честно, я сам не сразу придумал, как это можно реализовать, к счастью статья объясняет суть дела чётко и подробно.

Успехов в разработке, надеюсь, вы сможете создать по настоящему сложные приложения, на пике IT-прогресса


Оригинал: On-Demand Javascript
Переводчик: Додонов Александр Артурович

JavaScript-по-запросу

Вкратце

JavaScript подгружаемый с сервера

История

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

Задача

Как развернуть на сайте множество Javascript кода?

Причины

  • Приложения на Аяксе используют большое количество Javascript. Чем сложнее поведение онлайн-приложения, тем больше кода мы должны загрузить.
  • Загрузка Javascript – это удар по производительности. Взаимодействие с пользователем не может начаться пока не будут загружены все начальные Javascript модули.
  • Если у нас очень много Javascript-контента, внушает беспокойство напрасная загрузка интернет канала, ведь зачастую не все загруженные скрипты действительно используются.

Решение

Вместо скачивания всего кода при загрузке страницы - загружать Javascript только по мере необходимости. Это подход «Ленивая загрузка» в применении к Javascript и он имеет несколько достоинств:

  • Первоначальная загрузка страницы становится быстрее.
  • Совокупная загрузка интернет-канала меньше, за счёт того, что загружаются только нужные Javascript.
  • Развёртывание проще, так как код следит за загрузкой Javascript-скриптов, тогда как кодер только проверяет достаточно ли их, чтобы сбросить процесс.
  • Вы можете изготовлять сниппеты Javascript (небольшие фрагменты повторно используемого кода) на лету – эффективный возврат некоего «сообщения о поведении» подсказывает вашему браузер о том, какое действие будет следующим. Этот вариант ядра нашего шаблона описан в конце раздела.
  • Вы можете обойти стандартную политику «одного домена», что обычно требует Междоменного Прокси(Cross-Domain Proxy). Подготовленный сервер выставляет сегмент валидного Javascript кода, который может быть подгружен на сайт.

Обычно, хорошая практика – это включать Javascript ненавязчиво, добавив его с помощью одного и нескольких тэгов script.


  <html>
    <head>
      <script type="text/javascript" src="search.js">
      <script type="text/javascript" src="validation.js">
      <script type="text/javascript" src="visuals">
    </head>
    ...
  </html>
  

Javascript-по-запросу построен на идее предлагать небольшой по размеру модуль инициализации при загрузке страницы:


  <html>
    <head>
      <script type="text/javascript" src="init.js">
    </head>
    ...
  </html>
  

Модуль инициализации задаёт функциональность, которая необходима при запуске страницы и достаточна для типичного использования. Кроме того, он должен уметь подгружать по запросу требуемые JavaScript модули.

Итак, как подгрузить JavaScript после того как страница уже загружена? Первый способ - с помощью Вызова XMLHttpRequest. Браузер может принять ответ в виде Javascript и выполнить его. Любой код вне функции будет выполнен немедленно. Чтобы использовать добавленную функциональность несколько позже, можно добавить её напрямую к текущему объекту window или же к другому объекту, про который известно, что он существует. Например, может быть отправлено следующее:


 self.changePassword = function(oldPassword, newpassword) {
   ...
 }
 

Функции приёма просто нужно получить ответ как простой текст и передать его eval(). Внимание: здесь опять асинхронность поднимает свою уродливою башку. Нельзя быть уверенным, что новая функция появиться тут же. Не делайте так:


 if (!self.changePassword) {
   requestPasswordModuleFromServer();
 }
 changePassword(old, new) // Не сработает сразу, потому что
                          // changePassword ещё не загружена
        

Вам надо либо делать синхронный запрос к серверу либо проверять сущестование функции вручную или используя библиотеки, типа Narrative JavaScript и djax. Заметьте, вы не можете просто сделать цикл и проверять, не появилась ли новая функция, из-за однопоточной природы Javascript.

Менее навязчивый способ сделать то же самое подразумевает собой манипуляции DOM. Просто вставьте подходящий элемент script в DOM - и Javascript загрузиться. Его можно сделать дочерним к элементу head или body, а его свойство src должно указывать на URL Javascript. Есть несколько отличий от XMLHttpRequest:

  • Javascript будет автоматически выполнен практически также как будет выполнен Javascript указанный в статическом HTML при первом нахождении тэга <script>. Вы можете объявить скелетную функцию или переменную не добавляя её к объекту window.
  • Вы можете загружать Javascript с внешних доменов.
  • URL для src должен указывать на ресурс Javascript. XMLHttpRequest более гибок: вы можете, например, скачать несколько сниппетов Javascript внутри различных XML узлов.
  • DOM действует по другому. Даже если функциональность временная, скрипт всё равно будет добавлен к DOM, тогда как после вызова eval в методе XMLHttpRequest скрипт пропадает. (О совместимости cмотрите http://www.howtocreate.co.uk/loadingExternalData.html )
  • Вставка элемента script в DOM означает, что если файл был помещён в кэш браузера, будет использоваться кэш. С XMLHttpRequest это невозможно.

XMLHttpRequest вариант шаблона я называю Javascript овый Ответ (Javascript Response) намекая на шаблон HTML Ответ (HTML Response). Стандартный шаблон с DOM ссылается на ситуацию, где вы скачиваете новое множество функций, классов и переменных. Фактически, вы просто добавляете новых членов к DOM - другой код вызовет их. В Javascript Response вы захватываете сниппеты и помещаете их в eval выполняя скрипт не упакованный в функцию (хотя вы и можете определить несколько функций, а затем вызвать их в процессе выполнения). В этом случае, браузер спрашивает браузер, что делать дальше. Тогда как в стандартном шаблоне браузер знает что делать, но не знает как.

Одно важное применение Javascript-по-запросу связано с JSON Messages. Будучи обычным Javascript они могут быть загружены с помощью Javascript По-Запросу и это удобный способ перемещать семантический контент, в виде самоупакованного объекта. Так как Javascript может быть взять с третьей стороны, он обеспечивает механизм передачи семантического контента с внешних серверов в браузер без использования Кросс-доменного Прокси. Исходя из этого некоторые веб-сервисы, задуманные для общего использования в браузерах, выдают JSON Messages.

Если вы выставите JSON API с вашего сервера для использования внешним браузерами, вы должны побеспокоиться о некоторых важных вопросах безопасности. Все сервисы на основе JSON, которые зависят от авторизации на куках, является уязвимыми. Иначе, вы позволяете сайтам с вредоносным кодом делать междоменные запросы от имени пользователя к сервису JSON. В отличие от вызовов через XMLHttpRequest, который не может обратиться к внешнему домену или вызовов через IFrame где с внешнего домена не доступны детали родительского приложения, при работе с JSON такой защиты нет. Через переопределение объектов Javascript, таких как переписывание конструктора Array хакеры с evil.com могут отправить авторизующий JSON запрос на bank.com, и затем сделать что им заблагорассудится с ответом, например, загрузить его на свой сервер. Что бы предотвратить это: а) будьте уверены, что JSON обслуживает только общедоступные данные, то есть, что ему не нужна авторизация на куках, или б) если ваше приложение зависит от вызова завязанных на куки JSON служб с вашего собственного сервера, сделайте службы ориентированными на POST запросы, таким образом они не могут быть вызваны с помощью включения скрипта в тэг (которое всегда использует GET) Это значит, что ваш скрипт на браузере должен будет испустить XMLHttpRequest POST запрос, а затем обработать eval() ответ. К счастью, скрипту полученному с внешнего сервера не будет разрешено отправить такой же запрос.

Примеры из реальной жизни

Authenteo

Authenteo это объединение JavaScript фреймворка и CMS. Authenteo использует модель постоянных объектов, в которой логика для приложений определена в JavaScript внутри сохранённых объектов. Сохранённые объекты, а также и функции JavaScript внутри них, загружаются по запросу, используя формат данных JSPON (расширение JSON) Authenteo использует Narrative JavaScript (Прим. переводчика: ссылка устарела), чтобы реализовать последовательное выполнение, Authenteo может делать настоящие встроенные загрузки-по-запросу кода JavaScript и данных не совершая синхронизирующих (блокирующих) удалённых вызовов. Загрузка-по-запросу осуществляется прозрачно, каждый раз, как требуются данные. Authenteo использует символ #, чтобы отметить сохраняемые поля (используется прекомпилятором). В примере ниже указана ссылка на сохранённый объект и функцию Authenteo:


  myobj.#anotherObject.#render();
  

Authenteo прозрачно восстановит объект, упомянутый другим объектом "anotherObject" и функцию render, их загрузка уже встроена в эту конструкцию. Authenteo также использует предварительный разбор для перемещения объектов.

MapBuilder

MapBuilder - это фреймворк для сайтов-карт. Он использует Javascript-по-запросу, чтобы сократить количество загружаемого кода. Приложение сделано на основе парадигмы Модель-Представление-Контроллер. Информация модели объявлена в XML файле, вместе с Javascript необходимым для загрузки соответствующих виджетов. При запуске страницы загружается не весь JavaScript, а только необходимый. На сайте только частичная реализация этого паттерна: есть уверенность, что только минимальное необходимое подмножество Javascript модулей будет загружено, но нет «ленивой», по-запросу загрузки фрагментов кода.

Dojo Package System

Dojo - это всеобъемлющий фреймворк, цель которого упростить разработку на Javascript. Сам по себе он предоставляет множество скриптов, и в конкретных примерах нам понадобится только некоторые из них. Чтобы управлять скриптами, есть система пакетов похожая на ту что используется в Java, и она позволяет подгружать новый Javascript по мере необходимости. Вам нужно просто включить простой файл Javascript:


  dojo.hostenv.moduleLoaded("dojo.js.*");
  

Остальные пакеты вы можете скачать по запросу вот так:


  dojo.hostenv.moduleLoaded("dojo.aDojoPackage.*");
  

он загружает все соответствующие Javascript модули из dojo.aDojoPackage. Точный набор модулей определён автором dojo.aDojoPackage, и может быть сделан зависимым от окружения вызывающего (браузер или командная строка).

Система импортирования JSAN

The JavaScript Archive Network (JSAN) - онлайн-репозиторий скриптов. Также, он включает библиотеку для импортирования Javascript модулей, по удобству схожую с Java. Следующий вызов:


 JSAN.use('Module.To.Include');
 

преобразуется в Module/To/Include.js. JSAN.includePath определяет все возможные директории верхнего уровня, где этот путь может располагаться. Если includePath равно ["/", "/js"], JSAN будет смотреть в /Module/To/Include и /js/Module/To/Include.

twoBirds full AJAX - всё по запросу

Это AJAX система обеспечивает выдачу веб-объектов по запросу. В дополнение к загрузке .js файла по запросу, он также может динамически загружать .html.tpl и .css файлы в кеш структуру браузера. Данный веб-объект twoBirds имеет функцию init(), которая проверяет нет ли необходимых файлов в кеше и если нет, ждёт когда они загрузятся, а затем формирует html код элемента добавленного в DOM модель. Следующий вызов:


tb.element.show( 'target div id', '', '' );

загрузит и отобразит элемент на странице внутри заданного DIV элемента. twoBirds включает блочную структуру сайта основанную на модульных директориях.

В силу того, что веб-объекты twoBirds могут включать другие веб-объекты twoBirds, общий уровень сложности сайта ограничен лишь возможностью качественно управлять его кодом.

С 31 марта 2007 twoBirds V2.0.0 объявлен стабильным, смотрите прототип [1] . Свяжитесь с программистом frank.thuerigen@phpbuero.de если вы хотите получать копию этой библиотеки, так как документация и проверка работоспособности находятся сейчас на стадии разработки.

Learnhanzi flashcards

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

Иллюстрация рефакторинга

Внедрение Javascript-по-запросу на Wiki Demo

В основном wiki Demo, все Javascript загружались одновременно. Но в большинстве случаев пользователи только читают с вики, зачем же загружать код нужный для записи? Итак, мы преобразуем эту демку к Javascript-по-запросу, в три шага:

  1. Функция uploadMessage будет выделена в другой файл Javascript upload.js.
  2. Дальнейшим преобразованием будет закачивание upload.js только для процесса записи. Это будет версия, использующая подход с преобразованиями DOM.
  3. И, наконец, ещё один рефакторинг, мы заменим преобразования DOM на XMLHttpRequest.

Разделяем Javascript: выделение upload.js

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


  <script type="text/javascript" src="wiki.js"></script>
  *** <script type="text/javascript" src="upload.js"></script> ***
  

Новый upload.js содержит функцию uploadMessage. Отражая разделение, пара параметров будет представлять разделение функций в главном скрипте wiki.


 function uploadMessages(pendingMessages, messageResponseHandler) {
   ...
 }
 

Код вызова практически такой же:


 uploadMessages(pendingMessages, onMessagesLoaded);
 

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

Javascript-по-запросу на основе DOM

Этот рефакторинг и следующий, оба реализуют Javascript-по-запросу, но разными способами. По существу нельзя сказать, какой из них лучше - каждый подход имеет свои особенности уже обсуждённые в пункте Решение.

Если у нас Javascript-по-запросу, upload.js уже не нужен при начальной загрузке страницы, таким образом ссылка на него с HTML страницы исчезает, остаётся только главный модуль wiki.js.


 <script type="text/javascript" src="wiki.js">
 

В этом файле должна быть добавлена новая функция подгрузки скрипта. Чтобы избежать множественной загрузки одного и того же скрипта делается проверка на наличие uploadMessages, функции которая подгружается по запросу. Мы добавим элемент script в тэг head документа, проинициализировав его URL’ом по которому находится upload.js и стандартным атрибутом type для Javascript. Этот новый элемент DOM является сердцем Javascript'а-по-запросу на основе DOM.


 function ensureUploadScriptIsLoaded() {
   if (self.uploadScript) { // уже есть
     return;
   }
   var head = document.getElementsByTagName("head")[0];
   script = document.createElement('script');
   script.id = 'uploadScript';
   script.type = 'text/javascript';
   script.src = "upload.js";
   head.appendChild(script)
 }
 

Вызов скрипта осуществляется просто вызовом этой функции. Однако, как уже отмечалось в разделе Решение, эта загрузка асинхронна, поэтому должна быть обязательно сделана ещё одна проверка.


 ensureUploadScriptIsLoaded();
 if (self.uploadMessages) { // Если ещё не загружено, подождать до следующей синхронизации
   uploadMessages(pendingMessages, onMessagesLoaded);
   ....
   

В первое время проверка, скорее всего, будет терпеть неудачу, потому что управление вернётся с функции загрузки раньше, чем произойдёт сама загрузка. Но в этом конкретном приложении, это не важно – в любом случае каждые пять секунд запускается последовательность полной синхронизации. Если функция uploadMessages ещё не появилась, она будет здесь через пять секунд, что нормально для наших целей. Если есть желание, мы можем поступать агрессивней - например, скрипт будет загружен, сразу же как началось редактирование.

Javascript-по-запросу на основе XMLHttpRequest

Манипуляции с DOM мы преобразуем в использование вызова XMLHttpRequest. Запрос к серверу будет возвращать upload.js, который будет помещён в eval(). Как и раньше, upload.js больше не появляется в HTML. Также остаётся на прежнем месте вызов ensureUploadScriptIsLoaded. Инициализирующий скрипт - wiki.js отличается только реализацией приёма Javascript. Ответ сервера в виде текста просто передаётся в eval.


 function ensureUploadScriptIsLoaded() {
   if (self.uploadMessages) { // Already exists
     return;
   }
   ** ajaxCaller.getPlainText("upload.js", function(jsText) { eval(jsText); }); **
 }
 

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


 function(pendingMessages, messageResponseHandler) {
   ...
 }
 

то она после выполнения eval пропадёт. Вместо этого мы должны достичь того же самого эффекта, изменив объект браузера window.


 self.uploadMessages = function(pendingMessages, messageResponseHandler) {
   ...
 }

Связанные шаблоны

HTML Ответ (HTML Response)

Javascript’овый Ответ - компаньон HTML Ответа , они следуют похожей сервероцентрированной философии, по которой сервер должен поставлять инструкции браузеру.

Предварительный разбор (Predictive Fetch)

Примените Предварительный разбор к Javascript-по-запросу если вы можете предугадать, что вскоре будет произведена загрузка Javascript.

Кэш на стороне браузера (Browser-Side Cache)

Природа загрузки Javascript такова, что разновидность кэша на стороне браузера появляется сама собой. Значит, обычно сохраняются результаты вызовов и положительная черта этого факта в том, что они автоматически добавляют новые функции в DOM.

Загрузка в несколько этапов (Multi-Stage Download)

Javascript-по-запросу - это вариант Загрузки в несколько этапов. Акцент в Загрузке в несколько этапов больше на загрузке семантики и отображении содержания, чем на Javascripts. В дополнение, этот шаблон скорее о загрузке согласно заранее спланированной последовательности, чем о загрузке по запросу.

Визуальная метафора

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

Хотите знать больше?

Dynamic data using the DOM and Remote Scripting Руководство от Томаса Бретли


В дальнейших выпусках будут новые переводы статей о шаблонах проектирования, но хочется их разбавлять, ведь сфера современного веб-программирования обширна. Поэтому спрашиваю читателей: какие материалы, по вашему мнению, обязательно стоит перевести на русский язык? Ваши предложения вы можете отправлять мне по адресу adodonov@gmail.com

Всего вам наилучшего,
Додонов Александр


В избранное