Володя Колесников
Инклюд в яваскрипте 19 марта 2007 |
Задача. | Упростить создание больших проектов. | ||
Современный крупный сайт невозможно представить без яваскрипта, и чем ближе разработчик желает приблизить свое приложение к тому, что называется вебом 2.0, тем больше становится доля яваскрипта в общем объеме программного кода.
Большое число скриптов труднее структурировать, а элемент <head>
каждой страницы превращается в нечто подобное:
01
02
03
04
05
06
07
08
<script src="/js/als/widget/Box.js" type="text/javascript" encoding="UTF-8"></script> <script src="/js/als/utils/Text.js" type="text/javascript" encoding="UTF-8"></script> <script src="/js/domain/ClientsInfo/Widget/Properties.js" type="text/javascript" encoding="UTF-8"></script> <script src="/js/domain/ClientsInfo/Widget/Address.js" type="text/javascript" encoding="UTF-8"></script> <script src="/js/domain/ClientsInfo/Widget/Person.js" type="text/javascript" encoding="UTF-8"></script> <script src="/js/domain/ClientsInfo/Widget/Company.js" type="text/javascript" encoding="UTF-8"></script> <script src="/js/domain/ClientsInfo/Page/Index.js" type="text/javascript" encoding="UTF-8"></script> ... и еще 30 таких же ...
Но это полбеды. Ведь файл Page/Index.js зависит от Company.js, а тот в свою очередь от Widget/Person.js и Widget/Address.js. А те от Widget/Date.js и Box.js и т. д.
Причем загружать их нужно именно в указанной последовательности. А если страниц много? А если, например, хочется разделить какой-нибудь из виджетов на два файла? Или добавить пару новых классов? Или объединить несколько скриптов в один большой файл для ускорения загрузки?
Ведь все зависимости удобно было бы хранить непосредственно в js-файлах.
Почти в любом «взрослом» языке для этого существует конструкция include. В яваскрипте ее нет, но при должном желании ее удается сымитировать. Представьте, как удобно было бы написать в начале Company.js что-нибудь вроде:
01
02
03
04
05
06
07
js.include('als.Template'); js.include('als.utils.Text'); js.include('als.widget.Box'); js.include('domain.ClientsInfo.Widget.Properties'); js.include('domain.ClientsInfo.widget.Address'); js.include('domain.ClientsInfo.widget.Person'); ...
Что же мешает так сделать? Или веб 2.0 способен лишь на раскрашивание кнопок?
Какие препятствия поджидают нас? Во-первых, инклюд должен полностью отрабатывать до начала выполнения кода. Во-вторых, не исключены зависимости нескольких файлов от одного модуля и загружать его дважды совсем не хочется. В-третьих, требуется какой-нибудь механизм загрузки файлов с сервера. Для начала препятствий хватит.
На дворе 2007 год, и подавляющее большинство браузеров поддерживают XHTTPRequest
именно им и следует воспользоваться. Этот объект работает в двух режимах: асинхронном (когда указывается функция обратного вызова) и синхронном (запрос происходит непосредственно во время вызова xhttp.open()
). Нам нужен второй: при этом код загрузится прямо на месте js.include
. А дальше достаточно выполнить eval(xhttp.responseText)
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
//js.js js = {}; js.loadedModules = {}; ┘ js.include = function(path) { if(js.loadedModules[path]) return; var transport = js.getXHTTPTransport(); transport.open('GET', js.rootUrl + path.replace(/\./g, '/') + '.js', false); transport.send(null); var code = transport.responseText; eval(code); }
Вот и все. Теперь перед загрузкой скриптов достаточно подключить js.js и инклюд работает.
Полезно вынести в отдельную команду объявление загруженного модуля. Например, в начале каждого файла писать js.module("js.Event")
, а саму функцию сделать так:
01
02
03
js.module = function(path) { js.loadedModules[path] = true; }
Теперь инклюд будет знать о том, что скрипт с данным адресом уже загружен, даже если js-файл подключен с помощью тега <script>
.
Ну а кроме того, каждая загрузка скрипта отдельный запрос на сервер. Поэтому если всегда необходимо загружать группу файлов как единый набор, оптимально объединить их в один большой. Тогда и серверу придется выполнять меньше операций, и трафик между браузером и сервером сократится. А нам в итоге нужно написать:
01
02
03
04
05
06
07
08
js.module("domain.Class1"); domain.Class1 = function() {┘} js.module("domain.Class2"); js.include("domain.Class1"); domain.Class2 = function() {┘} domain.Class2.prototype = new domain.Class1();
Замечание | То, что объявление модуля вынесено в самое начало файла (до инклюдов), позволяет избежать зацикливания даже в тех случаях, когда между файлами возникает циклическая зависимость. |
Раз уж мы раскладываем файлы по папкам вроде domain/ClientsInfo/Widget/Person.js, логично было бы в файле Person.js иметь описание класса domain.ClientsInfo.Widget.Person. Но если попытаться создать класс domain.ClientsInfo.Widget.Person при несуществующем domain.ClientsInfo.Widget, интерпретатор яваскрипта выдаст ошибку. Между тем, проверять в начале каждого файла, что существует объект domain.ClientsInfo.Widget, а вместе с ним и domain.ClientsInfo, и просто domain, будет утомительно и громоздко.
Объявление неймспейса имен удобно вынести в функцию объявления модуля. Например, так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
js.evalProperty = function(object, name, value) { if(object) { if(!object[name]) object[name] = value || true; return object[name]; } return null; } js.evalPath = function(path, context, value) { context = context || window; var pos = path.indexOf('.'); if(pos == -1) { return js.evalProperty(context, path, value); } else { var name = path.substring(0, pos); var path = path.substring(pos + 1); var obj = js.evalProperty(context, name, value); return js.evalPath(path, obj, value); } } js.module = function(path) { js.loadedModules[path] = true; js.evalPath(path); }
Дебаггинг | Загруженный код выполняется с помощью функции eval(). Это означает, что в «Мозилле» отладить его не удастся. Visual Studio более покладиста, но не балует подсветкой синтаксиса. Проблема решается подключением отлаживаемых скриптов тегом |
Обычная (не требует других библиотек) 4,56 КБ,
сжатая 2,72 КБ,
для prototype.js 3,44 КБ,
для prototype.js сжатая 1,95 КБ.
© 19952024 Студия Артемия Лебедева
|