WebScript.Ru
C:\   главная  ::   о сайте  ::  каталог скриптов  ::  гнездо  ::  форум  ::   авторам  :: Новостройки ::   ХОСТИНГ  ::

|| разделы::
|| поиск по сайту::

|| реклама::
|| новости почтой::
Рассылки Subscribe.Ru ::



Новости сайта WebScript.Ru
Популярные статьи

Hot 5 Stories

|| рекомендуем::




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


Прислал: Андрей Овчаренко [ 10.05.2007 @ 15:11 ]
Раздел:: [ Веб-технологии ]


Введение

Работа приложения, применяющего Ajax-технологии, напрямую зависит от качества разработки JavaScript-кода, который выполняется на стороне клиента-браузера. JavaScript неожиданно для многих оказался достаточно мощным и гибким, чтобы воплотить проекты любой сложности. В то же время, разработка серьезных проектов затрудняется отсутствием в JavaScript встроенных средств поддержки модульного программирования. Разделение программного кода на модули (или говоря проще - на файлы) должно рассматриваться по крайней мере с двух различных точек зрения: 1) с точки зрения разработки приложения и 2) с точки зрения доставки приложения клиенту-браузеру. При разработке приложения удобнее работать с небольшими модулями (один класс=один модуль или одна функция=один модуль) и динамически загружать эти модули по мере необходимости. При доставке приложения идеально было бы загрузить весь необходимый (и только необходимый) JavaScript-код за одно обращение к серверу.

Рассмотрим, например, варианты доставки кода библиотеки x клиенту-браузеру. Библиотека x (x-browser - кросс-браузер) доставляется клиенту-браузеру или как единый файл x.js, или как набор модулей (x_core.js, x_dom.js и т.п.), в которых объединены сходные по назначению функции, или как единый файл, скомпонованный утилитой xc из файлов xWidth.js, xTop.js и т.п. для каждой html-страницы на основании анализа ее кода. Остается за кадром - какие средства поддержки модульности использовались на этапе разработки библиотеки х? Библиотеки dojo и jsolait включают средства динамической загрузки модулей при помощи Ajax-запросов к серверу. Библиотека jsolait поддерживает модульность в стиле один "модуль-пакет"=один файл, а библиотека dojo реализует более мелкое дробление на модули: один класс=один файл. Разработчики могут, разобравшись со всеми юридическими тонкостями лицензионных соглашений, изучить оригинальный объектно-ориентированный стиль dojo, jsolait или другой библиотеки, и испол ьзовать готовые решения поддержки модульного программирования на JavaScript.

Настоящая статья содержит анализ некоторых существующих решений поддержки модульного программирования на JavaScript. А так же фрагменты кода библиотеки автора, которая поддерживает модульность, множественное наследование, Ajax-запросы и реализует компоненты для веб-браузера - плавающие окна, lookup-combobox. Полный код библиотеки JavaScript автора и тестовый пример доступны по адресу //comb-in.narod.ru. Статья создавалась параллельно с разработкой библиотеки (как прием организации разработки). Поэтому код библиотеки может отличаться в деталях от фрагментов кода, приведенных в статье.

Пространства имен в JavaScript

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

В JavaScript существует всего два пространства имен: глобальное и локальное - внутри тела функции (вложенные функции образуют пространство "локальное в локальном" - любой степени вложенности). Это не мешает разработчикам использовать привычную точечную нотацию так, будто бы в JavaScript поддерживались пространства имен:

// Подготовка объекта поддержки пространства имен
var com={} // то же, что var com=new Object()
com.aovc={}
com.aovc.html={}
com.aovc.html.test={}
// объект com.aovc.html.test будет использоваться для эмуляции пространства имен

com.aovc.html.test.alert=function(){// анонимная функция
alert(arguments[0]) // массив arguments доступен в теле функции
}
com.aovc.html.test.greeting="Hi"
com.aovc.html.test.alert(com.aovc.html.test.greeting)

Самое очевидные неудобство - слишком длинные имена и необходимость инициализации глобальной namespace-переменной (com) и namespace-объектов (com.aovc и т.д.). Но не только это. Если Вы используете готовые библиотеки - переменная com может быть уже занята. Кроме того, объект com.aovc может быть уже инициализирован раннее выполнявшимся кодом. Поэтому более строго следует написать так:

if (typof(com)== "undefined") // (com == undefined) допустимо, но не кросс-браузерно
var com={}
if (typeof  com.aovc == "undefined") // допустимо
com.aovc={}
...

Код стал еще более громоздким. Явно прослеживается необходимость в объекте Namespace и/или Module и/или Package. Такой объект создан в библиотеке jsolait. Минуя технические подробности покажем как используется объект Module из библиотеки jsolait:

var tutorial0 = importModule("tutorial");
var obj = new tutorial0.SubClass(3,4);
alert(obj.foo(3));

Внутри импортируемого модуля tuturial.js (из библиотеки jsolait) все имена переменных и имена функций локальны, т.к. расположены в теле предопределенной функции moduleScope(mod) и доступны по коротким именам (mod.имя). При импорте модуля tutorial.js, если только Вы находитесь в глобальном пространстве - уникальность имен (в данном случае var tutorial0) все равно остается на Вашей ответственности. В библиотеке dojo новое локальное пространство на глобальном уровне организуется при помощи несложного "трюка". Блок кода заключается в "операторные скобки", в качестве которых выступает определение анонимной функции и немедленный вызов этой функции в одном операторе. Этот "трюк" позволяет заменить отсутствующие в языке JavaScript блоки операторов с локальной видимостью переменных.

В следующем фрагменте кода оператор void используется для вызова анонимной функции "на лету", то есть в одном операторе с определением. (В JavaScript только оператор function(){...} создает новое локальное пространство но не операторы {...}, for(){...}, if(...) и т.д. Вместо использования оператора void можно заключить определение анонимной функции в круглые скобки):

void function(){ // без оператора void код не работает

var document=0 // переменная внутри функции - локальная
var navigator=1  // то же
var window=2     //  то же

alert(document+navigator+window)

}() // эта пара круглых скобок немедленно вызывает анонимную функцию

var nowork="Глобальная"

{  // а такая защита не работает
var nowork="Тоже глобальная"
}

alert(nowork) //Выведет значение "Тоже глобальная"

Мы определили анонимную функцию и "на лету" вызвали ее. Опреатор void в начале и круглые скобки в конце использованы для вызова функции в одном операторе. Хотя это неочевидно, но без оператора void код не работает.

Далее будем оптимизировать код для создания и использования объекта поддержки пространства имен. Наша цель использовать этот объект примерно так:

void function(){ // открываем локальное пространство имен

// локальная ссылка на глобальную переменную с "длинным" именем
var test=com.aovc.js.util.getOrCreateNamespace("com.aovc.html.test")

test.alert=function(){alert(arguments[0])}
test.greeting="Hi"
test.alert(package.greeting)

}() // закрываем глобальное пространство имен и вызываем анонимную функцию "на лету"

Реализация функции getOrCreateNamespace() также взята в "операторные скобки" и привязана к пространству имен (namespace-объекту) "com.aovc.js.util":

void function(){ // открываем новое локальное пространство

// Заготовка для будущего класса
var LocalNamespace=function(name){this.name=name;}
LocalNamespace.prototype={}

var localGetOrCreateNamespace=function(sNamespace){
var aNamespace = sNamespace.split(".")
var currentNamespace=""
var oNamespace=null
for (var i=0;i<aNamespace.length;i++){
if (i==0)
currentNamespace+=aNamespace[i]
else
currentNamespace+="."+aNamespace[i]
if (eval("typeof("+currentNamespace+")")=="undefined")
oNamespace=eval(currentNamespace+"=new LocalNamespace('"+currentNamespace+")'")
else
oNamespace=eval(currentNamespace)

}// endfor
return oNamespace;
}// end localGetOrCreateNamespace

var nsUtil=localGetOrCreateNamespace("com.aovc.js.util") // создаем объект пространства имен
nsUtil.getOrCreateNamespace=localGetOrCreateNamespace // регистрируем локальную функцию в глобальном объекте
}()

Теперь, после (!) загрузки кода библиотеки com.aovc.js.util наш код будет работать как и предполагалось.

void function(){ // новое локальное пространство
var test0=com.aovc.js.util.getOrCreateNamespace("com.aovc.html.test")
test0.alert=function(){alert(arguments[0])}
}() //вызываем анонимную функцию "на лету"

void function(){ // новое локальное пространство
var tuturial0=com.aovc.js.util.getOrCreateNamespace("com.aovc.html.test")// tuturial0=com.aovc.html.test
tuturial0.greeting="Hi"
tuturial0.alert(tutorial0.greeting)
}() //вызываем анонимную функцию "на лету"

Строгий стиль требует, чтобы корневая namespace-переменная (com) была явно инициализирована на глобальном уровне с использованием ключевого слова var:

if (typeof com=="undefined")
var com={/*new Object()*/};
void function(){ // открываем новое локальное пространство
...

Для большинства браузеров первое появление переменной com при вызове eval("com={}") в функции getOrCreateNamespace() слева от оператора присваивания без ключевого слова var создаст глобальную переменную com. Но использовать такую вольность нет необходимости.

Подведем итоги. 1) Использование анонимной функции и ее вызов "на лету" в качестве "операторных скобок" позволяет защитить глобальное пространство от случайной перезаписи одноименных переменных. 2) Использование глобальных namespace-объектов (например com.aovc.js.util) позволяет эмулировать пространство имен, использовать привычную точечую нотацию и упорядочить глобальное пространстве. 3) Функция getOrCreateNamespace("...") - "импортирует" глобальный объект в локальную область видимости. Такой подход защищает namespace-объекты от случайной перезаписи и делает код более прозрачным. Дальнейшее развитие этой идеи - обеспечить при "импортировании" пространства имен динамическую загрузку модулей в браузер при разработке и автоматическую компоновку модулей для доставки в браузер на этапе эксплуатации приложения.

Пакеты, модули и загрузчики для разработки/тестирования и для доставки клиенту-браузеру

Пакеты, модули, пространства имен, классы - эти понятия часто рассматриваются в тесной связи. С некоторых пор, класс java.lang.Terminator мы ищем в подкаталоге /lang каталога /java в файле Terminator.java (загрузчик ищет в файле Terminator.class). Если представить иное положение вещей - разобраться что и где искать было бы очень сложно. Требование Java распологать public класс/интерфейс в отдельном файле - может показаться для JavaScript-разработчиков излишне утомительным. Поэтому рассмотрим несколько возможных вариантов разбиения кода на модули.

Сначала рассмотрим разбиение на модули с точки зрения разработки приложения.

Первый вариант. Разбиение на модули-пакеты. Поддержка такого разбиения реализуется наиболее легко. Файл, например module0.js, содержит определение нескольких классов и/или нескольких функций, а также код инициализации модуля. Файл расположен по пути /dir0/dir1/module0.js. К внутренним именам модуля можно обращаться по полному имени dir0.dir1.module0.fuction0() или "импортировать" модуль в локальную переменную, например mod0, и обращаться по сокращенному локальному имени mod0.function0(). Модуль может загружаться в клиент-браузер при первом "импорте" пакета/пространства имен ("dir0.dir1.module0")

Второй вариант. Разбиение на модули-функции. Файл, например function0.js, содержит определение всего одной (!) функции. Файл расположен по пути /dir0/dir1/function0.js. Функция доступна по полному имени dir0.dir1.function0(). Автоматически импортировать функцию при первом вызове очень сложно. Для загрузки можно использовать код инициализации пакета dir0.dir1, расположенный в одном каталоге с функцией function0 (например, dir0/dir1/__init__.js) . Код загрузки выполняется при "импорте" пакета/пространства имен ("dir0.dir1").

Третий вариант. Разбиение на модули-классы. Файл, например Class0.js, содержит определение одного public класса. (Разумеется, в JavaScript понятие "public класс" носит исключительно неформальный характер). Файл расположен по пути /dir0/dir1/Class0.js, доступен в пространстве имен dir0.dir1 и создается оператором new dir0.dir1.Сlass0(). В идеале, код класса должен загружаться при первом создании экземпляра класса (потомка класса), хотя реализовать это сложнее.

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

Первый вариант. Компоновка всех модулей библиотеки в единый файл без учета применяемости классов и функций в коде конкретной html-страницы. Положительный момент - используется единственный файл для всех страниц и один тэг <script src=... > (Пример - библиотеки Prototype, Rico, x)

Второй вариант. Компоновка части модулей библиотеки в единый файл с учетом применяемости классов и функций в коде для каждой (!) html-страницы отдельно. В библиотеке x для этого используется утилита xc, запускаемая из командной строки. Лучше, если за компоновку отвечает серверный код (например фильтр). (Пример - библиотека x)

Третий вариант. Динамическая (Ajax-запросом) или статическая (в тэге <script src=... >) загрузка модулей-пакетов (одни файл=один модуль-пакет), содержащих код тесно взаимосвязанных классов, функций. При этом модули для загрузки могут совпадать и не совпадать с модулями для разработки. (Пример - библиотеки jsolait, sarissa, x)

"Золотой" вариант - использование интегрированной среды разработки, которая делает все за Вас.

Рассмотрим принципы реализации модуля-пакета в библиотеке автора ( //comb-in.narod.ru). В отличие от модуля-класса и модуля-функции, - модуль-пакет содержит несколько взаимосвязанных классов и функций, которые расположены в одном пространстве имен, поэтому их удобно объединить в одном модуле и отлаживать совместно. Модуль-пакет и его пространсво имен взаимосвязаны так, что файл dir0/dir1/module0.js содержит модуль который доступен в пространстве имен "dir0.dir1.module0". Ниже приведен фрагмент модуля window из библиотеки автора

void function(){// открыли новое локальное пространство

com.aovc.js.util.registreModule("com.aovc.js.html.window")
var mod=com.aovc.js.util.getOrCreateNamespace("com.aovc.js.html.window")

mod.focuseWindow=function(win){...}

function localFocuseWindow(win){..}
...
}() // закрыли новое локальное пространство и выполнили код  анонимной функции

Функция com.aovc.js.util.getOrCreateNamespace("...") уже была разобрана выше. Возвращает ссылку на namespace-объект, если он уже существует или создает новый. Анонимные функции привязываются к этому namespace-объекту и становятся общедоступными (public). Любая не анонимная функция определенная в модуле - локальная.

Функция com.aovc.js.util.registreModule(...) - регистрирует имя модуля в объекте registerOfModule. Объект registerOfModule локальный для модуля "com.aovc.js.util". Поэтому он доступен только из функции определенной в том же модуле (com.aovc.js.util.registreModule(...)). Логика использования объекта такова: При первой загрузке модуля его код исполняется и имя модуля регистрируется в объекте-регистре. При повторном доступе - если модуль зарегистрирован в объекте - регистре - код не загружается и повторно не выполняется.

void function(){

var registerOfModule = registerOfModule || {} // определение пустого объекта-регистра
com.aovc.js.util.registreModule=function(strNameSpace){
registerOfModule[strNameSpace]=true;  // код регистрации модуля
}

}()

Обратим внимание, что переменная var registerOfModule определена локально внутри анонимной функции и защищена от случайного доступа извне. Доступ возможен только через функцию com.aovc.js.util.registreModule(), которая определена как анонимная функция и зарегистирована в глобальном объекте com.aovc.js.util. То есть реализована поддержка private/public переменных/функций.

Остается рассмотреть - как происходит загрузка модуля и доступ к его членам. Раннее ма рассмотрели функцию getOrCreateNamespace(strNameSpace), теперь расширим функциональность и при первом обращении к пространству имен - произведем загрузку кода модуля Ajax-запросом. Код приведен ниже.

void function(){ // открыли новое локальное пространство

var mod=...   // ссылка на текущее пространсто имен модуля
mod.importModule=function(strNameSpace){
if (mod.isRegistredModule(strNameSpace))
return
mod.sendRequest(strNameSpace.replace(/\./g,"/")+".js",null,"get",false,function(req){eval(req.responseText)})
return mod.getOrCreateNamespace(strNameSpace)
};

}() // закрыли локальное пространство

В подробностях реализации Ajax-запроса можно разобрать по исходному коду библиотеки. Код модуля выполняется функцией eval(req.responseText). Особенность работы этой функции такова, что код будет выполняться в текущем контексте и все имена функций будут локальны. Поэтому все общедоступные функции должны быть зарегистрированы в namespace-объекте (например mod.importModule=function(strNameSpace){...}). Исходя из того, что библиотека использует только единственную глобальную ссылку - ссылку на namespace-объект - такая особенность функции работы eval(...) не привносит затруднений.

Динамическая загрузка функций в JavaScript

Самое мелкое дробление программного кода JavaScript - разделение на модули-функции, когда один файл содержит определение ровно одной функции:

function function1(){
alert("inside function1")
}

Загрузку функций осуществляет скрипт, который расположен в одном каталоге с функцией и имеет предопределенное имя __init__.js. Этот скрипт выполняется при загрузке модуля с соответсвующим именем. Соответственно дополним код функции importModule()

mod.importModule=function(strNameSpace){
if (mod.isRegistredModule(strNameSpace))
return
try{
mod.sendRequest(strNameSpace.replace(/\./g,"/")+".js",null,"get",false,function(req){eval(req.responseText)})
}catch (ex){}
try{
mod.sendRequest(strNameSpace.replace(/\./g,"/")+"/__init__.js",null,"get",false,function(req){eval(req.responseText)})
}catch (ex){}
return mod.getOrCreateNamespace(strNameSpace)
};

Теперь при загрузке модуля "dir0.dir1.module0" будут выполняться два скрипта: dir0/dir1/module0.js и dir0/dir1/module0/__init__.js. Рассмотрим возможный вариант код скрипта __init__.js.

void function(){
com.aovc.js.util.registerModule("dir0.dir1.module0")
com.aovc.js.util.loadFunctions("dir0.dir1.module0",["function1","function2"])
//init code
...
}()

Функция registerModule() рассмотрена выше. Она отвечает за регистрацию загружаемых модулей для предотвращения повторного выполнения кода уже загруженного модуля. Код функции loadFunctions() выполняет загрузку функций Ajax-запросом и привязку функции к пространству имен (например: "dir0.dir1.module0"):

mod.loadFunctions=function(ns,aFunctions){
var ctx=mod.getOrCreateNamespace(ns)
for(var i=0;i<aFunctions.length;i++){
var currentFunction=aFunctions[i]
mod.sendRequest(ns.replace(/\./g,"/")+"/"+currentFunction+".js",
null,"get",false,function(req){eval(req.responseText);ctx[currentFunction]=eval(currentFunction)})
}
}

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

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

Реализация простого наследования стандартными средствами JavaScript

Реализовать простое наследование можно используя только встроенные средства JavaScript (не смотря на это, практически все современные библиотеки реализуют свою оригинальную поддержку наследования, в том числе множественного). Для того, чтобы реализовать простое (не множественное) наследование встроенными средствами JavaScript достаточно 1) в конструкторе класса-потомка вызвать метод call() функции-конструктора класса-родителя с неявным параметром this в качестве первого аргумента и 2) свойству prototype функции-конструктора класса-потомка присвоить ссылку на экземпляр класса-родителя:

function ChildObject(...){ParentObject.call(this,...)}
ChildObject.prototype=new ParentObject(...)

Именно такой подход использован в библиотеке dojo (поддержка наследования в dojo этим далеко не ограничивается):

dojo.lang.inherits = function(/*Function*/ subclass, /*Function*/ superclass){
// summary: Set up inheritance between two classes.
if(typeof superclass != 'function'){
dojo.raise("dojo.inherits: superclass argument ["+superclass+"] must be a function (subclass: ["+subclass+"']");
}
subclass.prototype = new superclass();
subclass.prototype.constructor = subclass;
subclass.superclass = superclass.prototype;
// DEPRECATED: super is a reserved word, use 'superclass'
subclass['super'] = superclass.prototype;
}

Оценим достоинства и недостатки такой реализации наследования. К достоинствам следует отнести максимальное использование стандартных средств JavaScript. Нет необходимости использовать дополнительный программный код, реализующий наследование. Конструктор класса-потомка вызывает конструктор класса-родителя, и так далее вверх по иерархии классов, что обеспечивает автоматическую инициализацию создаваемого экземпляра . Присоединение нового метода к прототипу базового класса немедленно обеспечивает доступ к этому методу из производного класса (даже в созданных ранее экземплярах). В то же время, присоединение метода к прототипу производного класса не влияет на базовsq класс. К недостаткам следует отнести невозможность реализовать множественное наследование и необходимость вызывать конструктор базового класса для создания прототипа производного класса. При вызове конструктора базового класса может происходить нежелательная инициализация объекта.

Поддержка загрузки классов и множественного наследования в JavaScript

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

mod.isa=function(toObject,fromObject){
for (var p in fromObject)
if (typeof(toObject[p])=="undefined") //not overload
toObject[p]=fromObject[p]
}
...
mod.isa(objRef,parentRef0)
mod.isa(objRef,parentRef1)

Далее, рассмотрим поддержку классов и наследования в тесной связи с поддержкой модульности. Выше были разобраны способы загрузки модулей-пакетов и модулей-функций. Третьим возможным способом разделения кода на модули являются модули-классы. Формально, модуль-класс не отличается от модуля-пакета, так как содержит определения нескольких функций и, возможно, код инициализации. Очевидным и принципиальным "смысловым" отличием является необходимость загрузить не только создаваемый класс, но и всех его предков. Сделать весь объем необходимой работы в операторе new не удасться, така как код конструктора может быть еще не загружен. Поэтому использование оператора new должно быть спрятано в коде функции-фабрики. А конечный пользователь библиотеки должен создавать классы примерно так com.aovc.js.clases.create("dir0.dir1.Class0",arg1,ag2). Очевидно, что функция create может осуществить все требуемые действия по загрузке кода и организации множественного наследования. Весь код, связанный с поддержкой множественного наследования и загрузки классов поместим в отдельный модуль com.aovc.js.classes.js. Функция create не использует практически ничего нового по сравнению с разобранным выше материалом.

mod.create=function(className){
var arg=[]
for (var i=1;i<arguments.length;i++)
arg[i-1]=arguments[i]
aovc.js.util.importModule(className)
var classConstructor=eval(className)
var objRef=new classConstructor()
objRef.parentList={}
objRef.parentList[className]=true;
objRef.derive=mod.derive
if(objRef.init){
objRef.init.apply(objRef,arg)
}
return objRef
}

Существует несколько причин помещать код инициализации не в конструктор, а в функцию init(). Основная из этих причин - удобство использования функции init.apply(). По такому пути идут практически все библиотеки (см. Prototype, jsolait). Результатом выполнения метода create будет загрузка скрипта Ajax-запросом, создание экземпляра и вызов метода init(). Поэтому условимся, что конструктор класса не будет содержать аргументов, вместо этого аргументы передаются методу init().

Теперь сосредоточим внимание на реализации наследования. Для реализации наследования в коде класса вызовем метод derive("dir0.dir1.ParentClass0"), который загрузит код соответствующего класса (без создания класса) и вызовет его метод init(). Вопросом является - где расположить вызов метода derive: в конструкторе или в методе init()? Поскольку мы условились передавать параметры только методу init() (но не конструктору) и метод derive() может передавать параметры методу init() родителя - место метода derive() в теле метода init(). Поэтому конструктор класса может быть (и должен быть) пустым и служить только техническим целям. (Все же в методе derive() код конструктора родителя вызывается)

mod.derive=function(className){
if (this.parentList[className]) // защита от цикличности в наслежовании классов
return this;
this.parentList[className]=true;
var arg=[]
for (var i=1;i<arguments.length;i++)
arg[i-1]=arguments[i]
aovc.js.util.importModule(className)
var parentConstructor=eval(className)
mod.isa(this.constructor.prototype,parentConstructor.prototype)
parentConstructor.call(this)
if(parentConstructor.prototype.init){
parentConstructor.prototype.init.apply(this,arg)
}
return this;
}

Очень важной подробностью является то, что методы присоединяются к прототипу объекта (а не к экземпляру в конструкторе - как это часто можно встретить в литературе). Принципиально важно, чтобы метод init() всегда был методом прототипа. Если необходимость в присоединении методов к экземпляру существует (например при использовании анонимных функций и замыканий) - присоединение методов необходимо производсить в методе init() или в любой части кода, но только не в конструкторе. Иначе одноименные методы родителя могут перезаписать методы потомка при вызове конструктора родителя в методе derive() (parentConstructor.call(this)). Повторим, что нет необходимости выполнять какой-либо код в конструкторе. Примерный код пользовательского класса следующий:

void function(){
module("aovc.js.test.Class0")
var mod=namespace("aovc.js.test")

mod.Class0=function(){} // конструктор
mod.Class0.prototype.init=function(a,b){
this.derive("aovc.js.test.Class1")
this.derive("aovc.js.test.Class2")
...
}

mod.Class0.prototype.alert=function(){alert("")}
}()

Сомневающимся в целесообразности развития модульного стиля программирования на JavaScript могу постараться передать то ощущение легкости, с которой был написан и отлажен модуль classses.js. Полный код утилитных модулей можно найти по адресу //comb-in.narod.ru/aovc/js/util.js и //comb-in.narod.ru/aovc/js/classes.js.

Заключение

Возникновение и развитие технологии Ajax заставило иначе взглянуть на возможности языка программирования JavaScript. Ближайшая аналогия может быть проведена между наблюдаемым ростом популярности JavaScript и историей развития языка Perl. Подобно Perl, JavaScript оказался достаточно мощным и выразительным для выполнения непредсказуемо возникающих новых задач в интернет-программировании. Если Perl сделал революцию в Internet как язык серверных CGI-сценариев, - JavaScript делает аналогичный прорыв в Internet со стороны клиента-браузера. Приведенный ниже фрагмент кода из библиотеки Prototype показывает, что программа на JavaScript может выглядеть очень привлекательно:

var Prototype = {
Version: '1.4.0',
ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)',
emptyFunction: function() {},
K: function(x) {return x}
}
var Class = {
create: function() {
return function() {
this.initialize.apply(this, arguments);
}
}
}
...

Сравнивая современное состояние программирования на языках JavaScript и Perl становится понятно, чего же не хватает и в какую сторону будет происходить развитие. Прежде всего - JavaScript не хватает стандартных библиотек расширения. Например, встроенный JavaScript-класс String обладает сравнительно небольшим, но мощным набором методов (включая работу с регулярными выражениями). Отсутствие таких привычных методов, как allTrim(), isEmpty() заставляет программистов самостоятельно реализовывать недостающие методы и присоединять их к прототипу класса String, чтобы использовать удобную точечную нотацию в стиле " my srting ".allTrim(). При этом, использование двух и более сторонних библиотек одновременно становится непредсказуемым (geopardic). Ведь каждая из них может перекрыть метод с одинаковым именем. Проблему может решить разработка стандартной pure-JavaScript библиотеки, которая обеспечит встроенные классы Object, Array, String "привычными" методами, а так же новыми классами, не включенными в стандарт языка.

Второе направление - выработка удобного, понятного и принимаемого всеми объектно-ориентированного стиля (или стилей) программирования на JavaScript. Анализ кода лучших библиотек показывает, что существуют разные подходы к применению объектно-ориентированных возможностей JavaScript. Такая замечательная библиотека, как x (x-browser, кросс-браузер) использует только средства, описанные в стандарте языка. В противоположность этому библиотека dojo (некоторые классы которой к сожалению не являются кросс-браузерными) реализует на основе встроенных объектных возможностей JavaScript свою очень развернутую систему поддержки классов (в том числе множественное наследование, пакеты). Библиотека jsolait отказывается от встроенных средств поддержки классов и реализует свою собственную объектную модель классов и поддержку модулей. А библиотека Prototype - реализует нестрогую, но естественную поддержку множественного наследования. Разработчик может "импортировать" любую из этих библиотек в h tml-тэге <script src="... .js" > и использовать готовые средства поддержки объектно-ориентированного программирования на JavaScript. Реально в качестве базовой чаще всего используется библиотека Prototype (и, конечно, x-browser для обеспечения кросс-браузерности приложений).

Третье направление - поддержка модульности (пакетов, модулей, загрузчиков, пространств имен) - средств которых нет в стандарте JavaScript. Перечислим имеющиеся решения. Библиотка x (x-browser) может включаться в html-страницу как 1) единый файл x.js; 2) один и более файлов, содержащих часть библиотеки x: x_core.js, x_dom.js и т.д.; 3) как файл скомпонованный (скомпилированный) утилитой xc на основании анализа html-страницы и содержащий только необходимые функции.

Эта часть модульности касается доставки библиотеки клиенту-браузеру. Но гораздо важнее модульность на этапе разработки. Если Ваша библиотека не только доставляется, но и разрабатывается как единый файл *.js - разработка серьезных проектов становится практически невозможной. Поэтому актуальна проблема разделения кода по модулям (то есть по отдельным файлам), их динамическая загрузка на этапе разработки и компоновка для доставки клиенту-браузеру при работе приложения. В такие библиотеки как x и Prototype средства динамической загрузки не предусмотрены (это не означает, что они не использовались на этапе разработки), библиотека dojo предусматривает размещение отдельного класса в отдельном модуле и динамическую загрузку средствами Ajax. Библиотека jsolait не привязывает модуль к каждому классу и также использует средства Ajax для динамической загрузки модулей.

Библиотека автора реализует поддержку модульности максимально опираясь на стандартные возможности языка. Код утилитных функций достаточно прост и очевиден. Все, кто захочет обсудить с автором приведенные идеи - просьба обращаться по адресу mailto:OvcharenkoAV@rambler.ru.

Овчаренко А.В. 1 марта 2007 года г.Харьков




 :::::  Овчаренко А.В. пишет 15.10.2008 @ 00:46 
Развитие идей, изложенных в статье и многое другое Вы можете найти в книге автора Овчаренко А. "Ajax на примерах". Книга построена на примерах разработки компонентов, использующих технологию Ajax для доступа к базам данных MySQL при помощи серверного программирования на PHP и клиентского программирования на JavaScript. Компоненты самого разного уровня сложности - от ставшего традиционным Аккордеона до достаточно сложного компонента Редактируемые таблицы данных. С полным содержанием униги можно ознакомиться по адресу [url] http://www.bhv.ru/books/full_contents.php?id=184616[/url].
Имя:
Email:
URL

Введите сумму двух чисел семь и шесть (семь+шесть=?)
Запомнить мою информацию

* Html запрещен* Ваш E-mail опубликован не будет.

Copyright © 2000-2001 WebScript.Ru nas@webscript.ru
Design © 2001 by Parallax Design Studio (aka Spectator.ru)
Все торговые марки и авторские права на эту страницу принадлежат их соответствующим владельцам.
Сгенерировано за: 0.0372829