Javascript предназначен для создания сценариев поведения сайта, это может быть как валидация форм, так и более сложные операции, такие как drag&drop или выполнение асинхронных запросов к серверу (например, Ajax). За последние нескольких лет Javascript библиотеки стали гораздо более популярны. Одной из причин этого, безусловно, является то, что веб-сайты становятся все более и более сложными и изобретать велосипед каждый раз уже не приемлемо, особенно, если у вас плотный график работы. Но оставим библиотеки и сосредоточимся на чистом Javascript, ведь это очень полезно — знать, какие есть паттерны программирования на Javascript.
В этой статье я попытаюсь представить некоторые из методов, которые я обнаружил. Я хотел бы отметить следующие паттерны:
- «Старая школа»;
- Одиночка (Singleton);
- Модули;
- Открытые модули;
- Объекты;
- Определение ленивых функций.
Я решил представить и сравнить эти различные паттерны решением одной и той же задачи каждым из них. Задача: Есть три ссылки на странице и при нажатии на любую из них, цвет ее фона следует изменить на заранее заданный для этой ссылки. Разметка страницы выглядит следующим образом:
<ul> <li><a href="http://news.bbc.co.uk/">News on BBC website</a></li> <li><a href="http://nytimes.com/">Frontpage of The New York Times</a></li> <li><a href="http://www.guardian.co.uk/">Guardian Unlimited</a></li> </ul>
Будем считать, что задача решена, если выполнены следующие условия:
- Цвета определены в какой-либо конфигурационной переменной;
- Получены все ссылки на странице и помещены в хранилище (массив);
- Сделан цикл по этому массиву и к каждому элементу привязано событие OnClick, указывающее на функцию, которая меняет цвет фона;
- Если по ссылке щелкнули, функция выполняется и цвет фона меняется.
«Старая школа»
Я хотел бы начать с демонстрации того, как эта задача была бы решена в конце 90-х, начале 2000 года. В то время, Javascript использовался в основном для написания «спагетти» кода, выполняющего одну операцию за другой. Никто не волновался о пространстве имен. Никто не беспокоился о повторном использовании кода. Итак есть 2 функции:
- anchorChange1: собирает ссылки и назначает события;
- changeColor1: меняет цвет фон.
Без дальнейших церемоний, представлю окончательный код:
function changeColor1(linkObj, newColor) { linkObj.style.backgroundColor = newColor; } function anchorChange1() { // настройка цветов var config = { colors: [ "#F63", "#CC0", "#CFF" ] }; // получаем ссылки на странице var anchors = document.getElementsByTagName("a"); var size = anchors.length; // перечисляем ссылки и назначаем события for (var i =0 ; i < size; i++) { anchors[i].color = config.colors[i]; anchors[i].onclick = function () { changeColor1(this, this.color); return false; }; } }
В Javascript блоке — после ссылок и, желательно, ближе к закрытию тега body — остается только вызвать основную функцию:
<script type="text/javascript"> anchorChange1(); </script>
Вот рабочий пример. Он отлично работает, хоть и есть проблема с загромождением глобального пространства имен. Это означает, что в гораздо более сложной ситуации с несколькими разработчиками, работающими над одним проектом, у вас могут быть большие проблемы, например, если кто-то сделает что-то вроде этого:
function changeColor1() { alert("Ох блин, мой код переопределили!"); }
Итак, если после вашего собственного определения changeColor1, есть второе определение функции с тем же названием, ваш код будет потерян. Как было сказано в введении, проекты сайтов стали гораздо более сложней в наши дни (с возможной работой несколько разработчиков над одним интерфейсом), чем они были 5 или 10 лет назад. Итак, есть потенциальная опасность, что кто-то перепишет чужой код, и отслеживание этой проблемы может стать полным кошмаром. Таким образом, я считаю следует вообще избегать этот метод.
Одиночка (Singleton)
Второе решение использует Singleton, что означает создание объекта и присвоение значений (например, функций) в его свойствах. Такой объект может быть создан, используя следующий синтаксис:
var testObject = {};
Для начала я создал пустой Singleton:
anchorChange2 = {};
Я решил создать 3 различных свойств этого объекта:
- сonfig: хранит различные фоновые цвета;
- alterColor: метод, который измененяет цвет;
- init: назначает функцию alterColor элементу.
Свойство сonfig хранит новые цвета:
config: { colors: [ "#F63", "#CC0", "#CFF" ] }
Метод alterColor задает новый цвет фона:
alterColor: function (linkObj, newColor) { linkObj.style.backgroundColor = newColor; }
Метод init отвечает за назначения событию onclick выполнения функции alterColor:
init: function () { var self = this; // сохраняем текущий объект в self var anchors = document.getElementsByTagName("a"); var size = anchors.length; for (var i =0; i < size; i++) { anchors[i].color = self.config.colors[i]; anchors[i].onclick = function () { self.alterColor(this, this.color); // привязываем функцию к событию return false; }; } }
Окончательный Singleton выглядит так:
// создаем класс и тут же используем его var anchorChange2 = { config: { colors: [ "#F63", "#CC0", "#CFF" ] }, // собственно установка фонового цвета alterColor: function (linkObj, newColor) { linkObj.style.backgroundColor = newColor; }, init: function () { var self = this; // сохраняем текущий объект в "self" // получаем все ссылки на странице var anchors = document.getElementsByTagName("a"); var size = anchors.length; for (var i =0; i < size; i++) { anchors[i].color = self.config.colors[i]; anchors[i].onclick = function () { self.alterColor(this, this.color); // привязываем функцию к событию return false; }; } } };
Вот рабочий пример.
Модули
Из singleton`а логично вытекает то, что Douglas Crockford называет паттерн «модуль». Идея в создании инкапсулированого модуля, который не может конфликтовать с другими модулями, которые вы, либо кто-либо другой, создал. Вы можете создавать публичные и защищенные методы в рамках этого модуля.
Во-первых, создадим функцию, которая запускается на выполнение немедленно (что обусловлено круглыми скобками после закрытия фигурных скобок):
anchorChange3 = function () {}();
Как отмечалось ранее, при этом паттерне, вы можете иметь публичные и защищенные методы. Публичные методы могут быть доступны снаружи, защищенные — только внутри объекта. Я решил сделать защищенное свойство config, защищенный метод alterColor, который измененяет цвет и 2 публичных метода, которые связаны с самим объектом. Для того чтобы это произошло, вы возвращаете объект с соответствующими свойствами:
return { // public метод // доступен снаружи changeColor: function (linkObj, newColor) { alterColor(linkObj, newColor); }, // public метод init: function () { var self = this; // сохраняем текущий объект в "self" // получаем все ссылки на странице var anchors = document.getElementsByTagName("a"); var size = anchors.length; for (var i =0; i < size; i++) { anchors[i].color = config.colors[i]; anchors[i].onclick = function () { self.changeColor(this, this.color); // привязываем функцию к событию return false; }; } } };
Функция alterColor, так же как и свойство config, находится внутри родительской функции anchorChange3, вне возвращаемого объекта:
var config = { colors: [ "#F63", "#CC0", "#CFF" ] } function alterColor(linkObj, color) { linkObj.style.backgroundColor = color; }
Так как alterColor является защищенным методом, он не может быть доступен снаружи. Для того чтобы выполнить эту функцию, необходимо либо положить его в объект (что снова приведет нас к использованию Singleton), либо создать другой метод в возвращаемом объекте, который и будет вызван. Я сделал метод changeColor, который затем вызывает alterColor.
Окончательный код для этого паттерна выглядит следующим образом:
var anchorChange3 = function () { // private свойство var config = { colors: [ "#F63", "#CC0", "#CFF" ] } // private метод // доступен только из anchorChange3 // не доступен снаружи function alterColor(linkObj, color) { linkObj.style.backgroundColor = color; } return { // public метод // доступен снаружи changeColor: function (linkObj, newColor) { // вызывает private метод, меняющий цвет alterColor(linkObj, newColor); }, // public метод // может быть вызван снаружи init: function () { var self = this; var anchors = document.getElementsByTagName("a"); var size = anchors.length; for (var i =0; i < size; i++) { anchors[i].color = config.colors[i]; anchors[i].onclick = function () { self.changeColor(this, this.color); return false; }; } } }; }();
Как и в Singleton`е, в теле документа нужно вызвать метод init:
<script type="text/javascript"> anchorChange3.init(); </script>
Вот рабочий пример.
Продолжение перевода.


отличная статья, спасибо!)))
Статья хорошая, но хотелось бы узнать как объявить переменную не в закрытом, а в открытом пространстве, что бы она была доступна внешним функциям, просто интересует синтаксис
@system_twister, к примеру, в anchorChange3 метод changeColor доступен снаружи, ничего не мешает добавить и переменные таким же образом.
var anchorChange3 = function () {
...
return {
public_var:'значение',
changeColor: function (linkObj, newColor) {
}
...
};
}();
anchorChange3.public_var; //так используем
Да, Я тоже так подумал, но что-то у меня эти переменные не видны внутри функций приватных этого же класса.
Где Я ошибаюсь?- никак не пойму! :(
var singlton1 = (
function func1(){var x=currtent.coordinate[0];var y=current.coordinate[1]; ... }
return{current:{ coordinate: [21,50]},init: function (){ ...}}
}())
быть может что-то типа этого:
var singlton1 = {
func1:function(){var x=this.coordinate[0];var y=this.coordinate[1];return [x,y]},
coordinate:[21,50],
init:function (){
return {current:{ coordinate: this.coordinate}}
}
}
singlton1.coordinate
singlton1.func1()