Шаблоны программирования Javascript (часть I)

Javascript предназначен для создания сценариев поведения сайта, это может быть как валидация форм, так и более сложные операции, такие как drag&drop или выполнение асинхронных запросов к серверу (например, Ajax). За последние нескольких лет Javascript библиотеки стали гораздо более популярны. Одной из причин этого, безусловно, является то, что веб-сайты становятся все более и более сложными и изобретать велосипед каждый раз уже не приемлемо, особенно, если у вас плотный график работы. Но оставим библиотеки и сосредоточимся на чистом Javascript, ведь это очень полезно — знать, какие есть паттерны программирования на Javascript.
В этой статье я попытаюсь представить некоторые из методов, которые я обнаружил. Я хотел бы отметить следующие паттерны:

  • «Старая школа»;
  • Одиночка (Singleton);
  • Модули;
  • Открытые модули;
  • Объекты;
  • Определение ленивых функций.

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

  1. <ul>
  2. <li><a href=”http://news.bbc.co.uk/”>News on BBC website</a></li>
  3. <li><a href=”http://nytimes.com/”>Frontpage of The New York Times</a></li>
  4. <li><a href=”http://www.guardian.co.uk/”>Guardian Unlimited</a></li>
  5. </ul>

Будем считать, что задача решена, если выполнены следующие условия:

  • Цвета определены в какой-либо конфигурационной переменной;
  • Получены все ссылки на странице и помещены в хранилище (массив);
  • Сделан цикл по этому массиву и к каждому элементу привязано событие OnClick, указывающее на функцию, которая меняет цвет фона;
  • Если по ссылке щелкнули, функция выполняется и цвет фона меняется.

«Старая школа»

Я хотел бы начать с демонстрации того, как эта задача была бы решена в конце 90-х, начале 2000 года. В то время, Javascript использовался в основном для написания «спагетти» кода, выполняющего одну операцию за другой. Никто не волновался о пространстве имен. Никто не беспокоился о повторном использовании кода. Итак есть 2 функции:

  • anchorChange1: собирает ссылки и назначает события;
  • changeColor1: меняет цвет фон.

Без дальнейших церемоний, представлю окончательный код:

  1. function changeColor1(linkObj, newColor) {
  2. linkObj.style.backgroundColor = newColor;
  3. }
  4. function anchorChange1() {
  5. // настройка цветов
  6. var config = {
  7. colors: [ “#F63”, “#CC0”, “#CFF” ]
  8. };
  9. // получаем ссылки на странице
  10. var anchors = document.getElementsByTagName(“a”);
  11. var size = anchors.length;
  12. // перечисляем ссылки и назначаем события
  13. for (var i =0 ; i < size; i++) {
  14. anchors[i].color = config.colors[i];
  15. anchors[i].onclick = function () {
  16. changeColor1(this, this.color);
  17. return false;
  18. };
  19. }
  20. }

В Javascript блоке — после ссылок и, желательно, ближе к закрытию тега body — остается только вызвать основную функцию:

  1. <script type=”text/javascript”>
  2. anchorChange1();
  3. </script>

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

  1. function changeColor1() {
  2. alert(“Ох блин, мой код переопределили!”);
  3. }

Итак, если после вашего собственного определения changeColor1, есть второе определение функции с тем же названием, ваш код будет потерян. Как было сказано в введении, проекты сайтов стали гораздо более сложней в наши дни (с возможной работой несколько разработчиков над одним интерфейсом), чем они были 5 или 10 лет назад. Итак, есть потенциальная опасность, что кто-то перепишет чужой код, и отслеживание этой проблемы может стать полным кошмаром. Таким образом, я считаю следует вообще избегать этот метод.

Одиночка (Singleton)

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

  1. var testObject = {};

Для начала я создал пустой Singleton:

  1. anchorChange2 = {};

Я решил создать 3 различных свойств этого объекта:

  • сonfig: хранит различные фоновые цвета;
  • alterColor: метод, который измененяет цвет;
  • init: назначает функцию alterColor элементу.

Свойство сonfig хранит новые цвета:

  1. config: {
  2. colors: [ “#F63”, “#CC0”, “#CFF” ]
  3. }

Метод alterColor задает новый цвет фона:

  1. alterColor: function (linkObj, newColor) {
  2. linkObj.style.backgroundColor = newColor;
  3. }

Метод init отвечает за назначения событию onclick выполнения функции alterColor:

  1. init: function () {
  2. var self = this; // сохраняем текущий объект в self
  3. var anchors = document.getElementsByTagName(“a”);
  4. var size = anchors.length;
  5. for (var i =0; i < size; i++) {
  6. anchors[i].color = self.config.colors[i];
  7. anchors[i].onclick = function () {
  8. self.alterColor(this, this.color); // привязываем функцию к событию
  9. return false;
  10. };
  11. }
  12. }

Окончательный Singleton выглядит так:

  1. // создаем класс и тут же используем его
  2. var anchorChange2 = {
  3. config: {
  4. colors: [ “#F63”, “#CC0”, “#CFF” ]
  5. },
  6. // собственно установка фонового цвета
  7. alterColor: function (linkObj, newColor) {
  8. linkObj.style.backgroundColor = newColor;
  9. },
  10. init: function () {
  11. var self = this; // сохраняем текущий объект в “self”
  12. // получаем все ссылки на странице
  13. var anchors = document.getElementsByTagName(“a”);
  14. var size = anchors.length;
  15. for (var i =0; i < size; i++) {
  16. anchors[i].color = self.config.colors[i];
  17. anchors[i].onclick = function () {
  18. self.alterColor(this, this.color); // привязываем функцию к событию
  19. return false;
  20. };
  21. }
  22. }
  23. };

Вот рабочий пример.

Модули

Из singleton`а логично вытекает то, что Douglas Crockford называет паттерн «модуль». Идея в создании инкапсулированого модуля, который не может конфликтовать с другими модулями, которые вы, либо кто-либо другой, создал. Вы можете создавать публичные и защищенные методы в рамках этого модуля.
Во-первых, создадим функцию, которая запускается на выполнение немедленно (что обусловлено круглыми скобками после закрытия фигурных скобок):

  1. anchorChange3 = function () {}();

Как отмечалось ранее, при этом паттерне, вы можете иметь публичные и защищенные методы. Публичные методы могут быть доступны снаружи, защищенные — только внутри объекта. Я решил сделать защищенное свойство config, защищенный метод alterColor, который измененяет цвет и 2 публичных метода, которые связаны с самим объектом. Для того чтобы это произошло, вы возвращаете объект с соответствующими свойствами:

  1. return {
  2. // public метод
  3. // доступен снаружи
  4. changeColor: function (linkObj, newColor) {
  5. alterColor(linkObj, newColor);
  6. },
  7. // public метод
  8. init: function () {
  9. var self = this; // сохраняем текущий объект в “self”
  10. // получаем все ссылки на странице
  11. var anchors = document.getElementsByTagName(“a”);
  12. var size = anchors.length;
  13. for (var i =0; i < size; i++) {
  14. anchors[i].color = config.colors[i];
  15. anchors[i].onclick = function () {
  16. self.changeColor(this, this.color); // привязываем функцию к событию
  17. return false;
  18. };
  19. }
  20. }
  21. };

Функция alterColor, так же как и свойство config, находится внутри родительской функции anchorChange3, вне возвращаемого объекта:

  1. var config = {
  2. colors: [ “#F63”, “#CC0”, “#CFF” ]
  3. }
  4. function alterColor(linkObj, color) {
  5. linkObj.style.backgroundColor = color;
  6. }

Так как alterColor является защищенным методом, он не может быть доступен снаружи. Для того чтобы выполнить эту функцию, необходимо либо положить его в объект (что снова приведет нас к использованию Singleton), либо создать другой метод в возвращаемом объекте, который и будет вызван. Я сделал метод changeColor, который затем вызывает alterColor.
Окончательный код для этого паттерна выглядит следующим образом:

  1. var anchorChange3 = function () {
  2. // private свойство
  3. var config = {
  4. colors: [ “#F63”, “#CC0”, “#CFF” ]
  5. }
  6. // private метод
  7. // доступен только из anchorChange3
  8. // не доступен снаружи
  9. function alterColor(linkObj, color) {
  10. linkObj.style.backgroundColor = color;
  11. }
  12. return {
  13. // public метод
  14. // доступен снаружи
  15. changeColor: function (linkObj, newColor) {
  16. // вызывает private метод, меняющий цвет
  17. alterColor(linkObj, newColor);
  18. },
  19. // public метод
  20. // может быть вызван снаружи
  21. init: function () {
  22. var self = this;
  23. var anchors = document.getElementsByTagName(“a”);
  24. var size = anchors.length;
  25. for (var i =0; i < size; i++) {
  26. anchors[i].color = config.colors[i];
  27. anchors[i].onclick = function () {
  28. self.changeColor(this, this.color);
  29. return false;
  30. };
  31. }
  32. }
  33. };
  34. }();

Как и в Singleton`е, в теле документа нужно вызвать метод init:

  1. <script type=”text/javascript”>
  2. anchorChange3.init();
  3. </script>

Вот рабочий пример.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: