пятница, 17 ноября 2006 г.

JavaScript - "Сlosure" следует переводить как "сокрытие"?

Всё-таки у меня есть сомнения насчёт того, что "closure" следует переводить как "сокрытие". Слово "hide" здесь может не причём. Но и не "замыкание".

Попробуем изобразить, что такое "closure", на таком примере:

function A()
{
var hi="Hello"
return function() { var name="Earl"; alert(hi+" "+name) }
}
var a = A()
a()



Итак, "closure", котрое находится в "a" выдаёт нам переменную из
своей внешней функции полюс свою локальную переменную.
На рисунке "closure" обозначено пунктирной линией.
"Closure" - это локальные переменные плюс внешние переменные анонимной функции
в "А".
Статья на Wikipedia Closure
отсылает нас к модели актёра, в которой говориться, что "если актёр имеет адрес одного из других актёров, то они состоят в знакомстве.
Ага! А наше "closure" имеет ссылки на переменные внешней функции, значит анонимная функция знакома с внешней функцией. А "close" - это близкий. Может "closure" корректней переводить как "приближение" или "близость"?

Рисунок 1

пятница, 10 ноября 2006 г.

ООП в Javascript, JS-самый малопонимаемый язык (c) Douglas Crockford

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

И на днях я выяснил, что в JS обьекты основываюся не на классовом подходе, а на прототипировании. См. Прототипное программирование

Почитав сайт замечательного человека Douglas Crockford у меня практически всё прояснилось.

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

Проблема в том, что JS это единственный широко распространённый язык, в котором иерархия обьектов создаётся через прототипирование. Из этого следует то, что крайне мало информации о том, как писать на таких языках.

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

Дальше я приведу несколько примеров. Примеры сопроваждаются UML диаграммами классов, на которых я попытаюсь изобразить как это бы выглядело
будь это сделано на языке основанном на классах.
В примерах могут быть ошибки и моменты
моего собственного непонимания. Критика приветствуется...

Пример №1
// создаём что-то типа класса с конструктором
// потом создём обьект.
// Представим, что хотим сделать машину, сначала создаём
// каркас для машины. А затем производим экземпляр машины,
// при этом сразу указываем как будет называться модель машины
////////////////////////////////////////////////////////////////

// создаём конструктор(хоть он и называется функция, но это как класс
// в классовом подходе)
function car(model)
{
// код прямо в теле обьекта, является его конструктором
// т.е. выпоняется прямо после создания обьекта
this.model=model
}

// создали обьект myCar и передали его конструктору
// параметр - имя модели
myCar = new car("Audi");
// смотрим, как называется модель нашей машины
alert(myCar.model)
Рисунок 1

Пример №2
// здесь мы создаём приватный(private) метод нашего обьекта машина(car)
// встраиваем в нашу машину систему АBS
// но управлять системой АБС можно только из самой машины,
// извне нельзя. Наша система приватная, личная.
// переменная сезона(season) тоже приватная и доступна только из
// обьекта "машина"(car)
//////////////////////////////////////////////////////////////////
function car(model)
{
// создаём приватная переменную "сезон"
var season="winter";
this.model=model
// а здесь создаём приватный метод обьекта, он будет доступен только
// из самого обьекта
function abs()
{
if(season == "winter")
alert("включаю ABS")
}
// метод АБС можно вызывать только из обьекта машина:
abs();
}


// создали обьект myCar и передали ему
// параметр - имя модели
myCar = new car("Audi");

// попытка посмотреть на какой сезон настроена машина оканчиватся неудачей,
// season - приватная переменная:
//alert(myCar.season)
// так же нельзя вызвать приватный метод abs:
//myCar.abs();
Рисунок 2


Пример №3

// то же самое, только метод "abs" публичный(public)
// то есть, извне можно включить и отключить систему АБС
// нашей машины. типа дистанционного пульта
//////////////////////////////////////////////////////////////////

function car(model)
{
this.model=model
this.season="summer"
// обьявляем, что метод abs нашего обьекта, это функция Mabs
this.abs=abs;
}
function abs()
{
// здесь мы обращаемся к публиной переменной season текущего(this) обьекта
if(this.season == "winter")
alert("включаю ABS")
}
// создали обьект myCar и передали его конструктору
// параметр - имя модели
myCar = new car("Audi");

// присваеваем публичную пременную нашего обьекта "car":
myCar.season="winter";

// вызываем публичный метод "abs"
myCar.abs();
Рисунок 3

Пример №4
// в этом примере мы создаём конструктор для двигателя,
// а потом соеденяем машину и двигатель вместе и
// получаем конструктор машины с двигаетелем,
// после этого можно создавать машины с двигателем. /
//////////////////////////////////////////////////////////

// это конструктор нашей машины
function car(model)
{
// создаём локальную переменную "сезон":
var season = "winter"
// публичной переменной обьекта this.model присваиваем значение переданное в качестве
// параметра:
this.model=model
// а здесь создаём приватный метод обьекта, он будет доступен только
// из самого обьекта
function abs()
{
if(season == "winter")
alert("Зима на дворе! включаю ABS")
}
// метод АБС можно вызывать только из обьекта машина:
abs();
}

// создаём конструктор для двигателя нашей машины
function engine()
{
this.cubic=2000
this.engineHello = "Привет я двигатель обёма "+this.cubic+" кубов. Поехали!!!"
}

// теперь внимание! говорим, что прототип нашей машины это двигатель
// то есть машина наcледует все свойства и методы двигателя
// Или можно сказать, соеденяем машину и двигатель вместе
car.prototype = new engine;

// создаём обьект myCar
myCar = new car("Audi");

// смотрим, какой двигатель у нашей машины
alert(myCar.engineHello);
Рисунок 4


Пример №5
// а здесь создаём конструкор тормозов(brake),
// а потом соеденяем машину, двигатель и тороз вместе
// после этого можно создавать машины с двигателем
// и тормозами.
//////////////////////////////////////////////////////////

// это конструктор нашей машины
function car(model)
{
// создаём локальную переменную "сезон":
var season = "winter"
// публичной переменной обьекта this.model присваиваем значение переданное в качестве
// параметра:
this.model=model
// а здесь создаём приватный метод обьекта, он будет доступен только
// из самого обьекта
function abs()
{
if(season == "winter")
alert("Зима на дворе! включаю ABS")
}
// метод АБС можно вызывать только из обьекта машина:
abs();
}

// создаём конструктор для двигателя нашей машины
function engine()
{
this.cubic=2000
this.engineHello = "Привет я двигатель обёма "+this.cubic+" кубов. Поехали!!!"
}

function brake()
{
this.brakeHello = "Привет! Я тормо-з-ззз";
}

//
// соеденяме вместе двигатель и тормоза
engine.prototype = new brake;

// а теперь всё это(двигатель с прицепленными тормозами)
// встраиваем в конструктор машины:
car.prototype = new engine;

// создаём обьект myCar

myCar = new car("Audi");
alert("- Эй тормоза?!\n- "+myCar.brakeHello+"\n- Ау, движок?!\n- "+myCar.engineHello)

Рисунок 5


Пример №6
// теперь создадим двигатель большего обьёма
// и на его основе соберём машину
//////////////////////////////////////////////////////////

// это конструктор нашей машины
function car(model)
{
// создаём локальную переменную "сезон":
var season = "winter"
// публичной переменной обьекта this.model присваиваем значение переданное в качестве
// параметра:
this.model=model
// а здесь создаём приватный метод обьекта, он будет доступен только
// из самого обьекта
function abs()
{
if(season == "winter")
alert("Зима на дворе! включаю ABS")
}
// метод АБС можно вызывать только из обьекта машина:
abs();
}

// создаём конструктор для двигателя нашей машины
function engine()
{
this.cubic=2000
this.engineHello = "Привет я двигатель обёма "+this.cubic+" кубов. Поехали!!!"
}

function brake()
{
this.brakeHello = "Привет! Я тормо-з-ззз";
}

function megaEngine()
{
this.cubic=6000
this.engineHello = "Привет я Mega-двигатель обёма "+this.cubic+" кубов. Поехали!!!"
}



//
// соеденяме вместе двигатель и тормоза
engine.prototype = new brake;

// а теперь всё это(двигатель с прицепленными тормозами)
// встраиваем в конструктор машины:
car.prototype = new engine;

// создаём обьект myCar

myCar = new car("Audi");
alert("- Эй тормоза?!\n- "+myCar.brakeHello+"\n- Ау, движок?!\n- "+myCar.engineHello)

// собираем машину с более мощным двигателем:
megaEngine.prototype = new brake;
car.prototype = new megaEngine;
myNewCar = new car("Audi");
alert("- Эй тормоза?!\n- "+myNewCar.brakeHello+"\n- Ау, движок?!\n- "+myNewCar.engineHello)
// итого, мы написали новый конструктор для двигателя megaEngine
// а потом использовали его для сборки машины с прежним кузовом и тормозами
/*
С точки зрения классовой модели, класс car должен унаследовать
все свойства и методы engine и megaEngine.
Но у нас так не происходит. В процессе выполнения программы мы
создаём конструктор car сначала на основе engine, а потом на основе megaEngin.
То есть конструктор car у нас динамически изменяется по ходу работы
программы. Это значит, что до того, как мы изменили прототип car можно было
собирать на его основе машины с простым двигателем, а после того, как
перегрузили его прототип обьектом megaEngine можно создавать на основе
конструктора car машины с более мощным двигателем.
В отличии от классовых языков, где классы создаются сначала,
а потом только используюся в программе, в JS можно по ходу выполнения
программы изменять конструторы обьектов.
Поэтому диаграмма классов не является верной для этого примера.
*/



Рисунок 6


Пример №7.1
/ берём пример №3, создаём
// машину на основании конструктора. а потом
// в уже готовом обьекте-машине заменяем систему АБС
// на другую.
//////////////////////////////////////////////////////////////////
function car(model)
{
this.model=model
// обьявляем, что метод abs нашего обьекта, это функция Mabs
this.abs=Mabs;
}

function Mabs()
{
// здесь мы обращаемся к публиной переменной season текущего(this) обьекта
if(this.season == "winter")
alert("включаю ABS")
}

// создали обьект myCar и передали его конструктору
// параметр - имя модели
myCar = new car("Audi");

// создаем и присваеваем публичную пременную нашего обьекта "car":
myCar.season="winter";

// перегружаем метод обьекта:
// (меняем систему АБС)
myCar.abs = function()
{
if(this.season == "winter")
alert("А таперича у меня новый чип! Включаю ABS")

}


// вызываем публичный метод "abs"
myCar.abs();


Пример №7.2

// кроме того, можно расширить любой из встроенных
// объектов JS, например String.
// Здесь мы "навешиваем" на обьект String метод "trim",
// который удаляет пробелы в конце и в начале строки.
// Каждая строка в JS это обьект типа String,
// таким образом у каждой строки появится метод "trim"
//////////////////////////////////////////////////////////////////

String.prototype.trim = function()
{
var t=this
while(t.indexOf(" ")==0)
t=t.substring(1,t.length)
while(t.lastIndexOf(" ")==(t.length-1))
t=t.substring(0,t.lastIndexOf(" "))
return t
}

s = " Java Script "

alert("до: '"+s+"', после: '"+s.trim()+"'")

Пример №8
// Опять о динамике JS.
// Обьявляем два конструктора.
// Затем обьявляем, что прототип машины(car)
// это двигатель (engine) и создаём два обьекта
// myCar и MyNewCar. после этого обьявлем,
// что у прототипа нашей машины есть метод radio.
// После этого, ы наших обьектов появляются методы radio.
// Получается, что можно к обьекту являющемуся прототипом,
// других обьектов добавить метод или свойство и оно
// автоматически появится у всех дочерних обьктов
/////////////////////////////////////////////////////////
function car(model)
{
this.model=model
}
function engine()
{
var cubic=2000
this.engineHello="я двигатель объёма "+cubic+". Поехали!"
}

// прототип машины - двигатель
car.prototype = new engine

// создаём два обьекта
myCar = new car("Audi")
myNewCar = new car("Ford")

// добавляем метод к прототипу машины:
car.prototype.radio = function()
{
alert("Я бортовая рация на "+this.model+". Приём уверенный.")
}
// у уже созданных обьектов появляется новый метод
myCar.radio()
myNewCar.radio()

Пример №9

В JS возможно некое подобие абстрактных классов — объекты, созданные без помощи оператора new. У таких объектов не может быть ни "братьев", ни "сестер" и они могут быть только использоваться для наследования (прототипирования) другими объектами-конструкторами.

// В этом примере мы создаём объект двигатель(engine), и используем
// его при сборке машины. Изготовить другой экземпляр обьекта
// двигатель, используя в качестве конструктора обьект двигатель(engine), нельзя
// Его нужно встроить в конструктор машины(car) и только после этого производить
// экземпляры машин.
/////////////////////////////////////////////////////////

// создаем обьект engine (двигатель)
engine=
{
year:2006,
engineHello:function()
{
var cubic=2000
alert("Я — двигатель "
+this.year
+" года производства, с объёмом "
+cubic
+" кубов,\n установлен на автомобиле модели "
+this.model)
}
}
// определяем объект-конструктор car
function car(model)
{
this.model=model
}
// расширяем car объектом engine
car.prototype = engine

// создаем myCar — экземпляр car
myCar = new car("Audi");
myCar.engineHello()

// попытка создать обьект на основе engine обречена на неудачу:
// myEngine = new engine
// Error: engine is not a constructor
NB! здесь при обьявлении объекта engine используется не оператор new, а синтаксис :
объект={свойство1:метод1, свойство2:метод2}

Рисунок 7


Пример №10

// В обьекте-конструкторе(car) заводим приватную
// переменную: AbsProducer - производитель системы ABS
// Метод abs - привелигированный и публичный.
// Привилегированный потому, что имеет доступ
// к приватным переменным обьекта.
//////////////////////////////////////////////////////////////////

function car(model)
{
// приватная переменная:
var AbsProducer="BMW"
this.model=model
this.season="winter"
// обьявляем, что метод abs нашего обьекта, это функция Mabs
this.abs = function()
{
// здесь мы обращаемся к публиной переменной season текущего(this) обьекта
if(this.season == "winter")
alert("включаю ABS"+" производства: "+AbsProducer)
}
}

// создали обьект myCar и передали его конструктору
// параметр - имя модели
myCar = new car("Audi")

// вызываем публичный метод "abs"
myCar.abs()
// попытка посмотреть производителя системы АБС заканчивается неудачей:
// alert(myCar.AbsProducer) - Undefinied

Примечаение:
Если в обьекте конструкторе написать:
this.abs = abs
и определить метод abs вне тела car,
то этот метод будет публичным, но не привилегированным,
то есть не будет иметь доступа к приватным переменным обьекта

Пример 11

Остался ещё важный момент, касающийся раскрытия темы сокрытий(closures).
Closures почему-то переводят, как замыкания. Хотя, по моему скромному мнению,
это именно сокрытия, потому что они "скрывают" или "закрывают" свой локальный контекст.
Часто в англоязычных источниках при описании сокрытий всречается именно слово "hide".

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

Пример, конечно, не очень удачный.

// Сокрытия (closures)
function car()
{
var model = "Audi"
// приватная переменная обьекта
var speed=1
// метод - коробка передач
// принимает в качестве параметра число,
// на которое следует увеличить или уменьшить скорость машины
this.gearbox = function(param)
{
// метод возвращает анонимную функцию
// в которой вычисляется скорость
return function() { speed+=param; alert(speed +' on ' + model) }
}
}

myCar = new car()

// в этой переменной храним информацию о первом переключении
// скорости
speedInfo1 = myCar.gearbox(1)

// здесь о втором перекючении
speedInfo2 = myCar.gearbox(2)

speedInfo1()
speedInfo2()
/*
Что получилось?
После того как мы два раза переключили скорость
нам доступна информация о скорости после первого переключения
и второго. Сокрытие, которое возвращает метод gearbox
хранит свой локальный контекст плюс ссылку на переменные обьекта.
Причём во время работы метода gearbox сокрытие имеет достум к
переменным обьекта.
*/


Подведём итоги.

На JS можно легко строить ПО с применением ООП. Более того, из-за "прототипной" и динамической природы JS, делается это легче и лаконичнее,
чем на языках с классами. Возможно динамическое изменение созданных обьектов, что вообще невозможно при работе с обьектами на основе классов.

Но есть проблема с пониманием прототипного программирования, недостаточности материала по этой теме. Негативную роль сыграли книги по JS, в которых прототипирование не освещалось должным образом.
Исторически так получилось, что JavaScript незаслужено не воспринимался, как "серьёзный" язык. Это связано с тем, что после заключения соглашения Netscape и Sun о продвижении Java в интернете, JavaScript стали позиционировать, как младшего брата Java для небольших сценариев на стороне клиента. Хотя Netscape выпускала Netscape Enterprise Server, на которых использовался JavaScript, как язык серверных сценариев.

Само название JavaScript многими признаётся как некорректное(изначально язык назывался Mocha, а потом LiveScript).

С ростом популярности техники AJAX возрос и интерес к JavaScript. Возникает вопрос: если мы на клиенте(в браузере) используем JS, так почему бы и на сервере не использовать JS?! Фирма Sun отреагировала на такую тенденцию и уже больше года работает над проектом Phobos. Кроме того доступны многие другие решения: Server-Side_JavaScript.

Среди которых наиболее зрелым является проект Helma. Во многих проектах возможно создание обьектов на базе классов Java, то есть если вдруг чего-то нет в JS, то можно воспользваться богатой библиотекой классов платформы Java.
Update:
Доступ к базам данных производится через JDBS, что позволяет применять
любую БД для которой есть JDBC-драйвер.
Немного про Helma. Helma это не просто серверный интерпретатор JS,
это фреймворк, позволяющий строить web-приложение в соотстствии с архитектурой Модель-Вид-Контроллер(MVC).