Статьи

Шаблоны проектирования JavaScript: Шаблон наблюдателя

В JavaScript часто возникает проблема. Вам нужен способ обновить части страницы в ответ на определенные события, используя данные, которые они предоставляют. Скажем, например, пользовательский ввод, который вы затем проецируете на один или несколько компонентов. Это приводит к большому количеству push-and-pull в коде, чтобы поддерживать синхронизацию всего.

Вот где может помочь шаблон проектирования наблюдателя — он позволяет связывать данные «один ко многим» между элементами. Эта односторонняя привязка данных может быть управляемой событиями. С помощью этого шаблона вы можете создавать повторно используемый код, который решает ваши конкретные задачи.

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

Я буду использовать ECMAScript 6 для иллюстрации шаблона. Да, будут классы, функции стрелок и константы. Не стесняйтесь исследовать эти темы самостоятельно, если вы еще не знакомы. Я буду использовать части ES6, которые содержат только синтаксический сахар, поэтому он переносим с ES5, если это будет необходимо.

И я буду использовать Test-Driven-Development (TDD) для работы над шаблоном. Таким образом, вы знаете, как полезен каждый компонент.

Новые языковые функции в ES6 делают несколько кратких кодов. Итак, начнем.

Обозреватель событий

Общий вид шаблона выглядит следующим образом:

EventObserver │ ├── subscribe: adds new observable events │ ├── unsubscribe: removes observable events | └── broadcast: executes all events with bound data 

После того, как я уточню шаблон наблюдателя, я добавлю количество слов, которое его использует. Компонент подсчета слов возьмет этого наблюдателя и соберет все вместе.

Для инициализации EventObserver выполните:

 class EventObserver { constructor() { this.observers = []; } } 

Начните с пустого списка наблюдаемых событий и делайте это для каждого нового экземпляра. Теперь давайте добавим больше методов в EventObserver для EventObserver шаблона проектирования.

Метод подписки

Чтобы добавить новые события, сделайте:

 subscribe(fn) { this.observers.push(fn); } 

Захватите список наблюдаемых событий и вставьте новый элемент в массив. Список событий представляет собой список функций обратного вызова.

Один из способов проверить этот метод на обычном JavaScript заключается в следующем:

 // Arrange const observer = new EventObserver(); const fn = () => {}; // Act observer.subscribe(fn); // Assert assert.strictEqual(observer.observers.length, 1); 

Я использую утверждения Node для тестирования этого компонента в Node. Точно такие же утверждения существуют как и утверждения Чая .

Обратите внимание, что список наблюдаемых событий состоит из скромных обратных вызовов. Затем мы проверяем длину списка и утверждаем, что обратный вызов находится в списке.

Метод отписки

Чтобы удалить события, сделайте:

 unsubscribe(fn) { this.observers = this.observers.filter((subscriber) => subscriber !== fn); } 

Отфильтруйте из списка все, что соответствует функции обратного вызова. Если совпадений нет, обратный вызов остается в списке. Фильтр возвращает новый список и переназначает список наблюдателей.

Чтобы проверить этот хороший метод, выполните:

 // Arrange const observer = new EventObserver(); const fn = () => {}; observer.subscribe(fn); // Act observer.unsubscribe(fn); // Assert assert.strictEqual(observer.observers.length, 0); 

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

Метод вещания

Для вызова всех событий сделайте:

 broadcast(data) { this.observers.forEach((subscriber) => subscriber(data)); } 

Это перебирает список наблюдаемых событий и выполняет все обратные вызовы. При этом вы получаете необходимое отношение «один ко многим» к подписанным событиям. Вы передаете параметр data который связывает данные обратного вызова.

ES6 делает код более эффективным с помощью функции стрелки. Обратите внимание на функцию (subscriber) => subscriber(data) которая выполняет большую часть работы. Эта однострочная стрелка обладает преимуществом этого короткого синтаксиса ES6. Это определенное улучшение в языке программирования JavaScript.

Чтобы проверить этот метод вещания, выполните:

 // Arrange const observer = new EventObserver(); let subscriberHasBeenCalled = false; const fn = (data) => subscriberHasBeenCalled = data; observer.subscribe(fn); // Act observer.broadcast(true); // Assert assert(subscriberHasBeenCalled); 

Используйте let вместо const чтобы мы могли изменить значение переменной. Это делает переменную изменчивой, что позволяет мне переназначать ее значение внутри обратного вызова. Использование let в вашем коде посылает коллегам-программистам сигнал о том, что переменная в какой-то момент изменяется. Это добавляет удобочитаемость и ясность вашему коду JavaScript.

Этот тест дает мне уверенность, необходимую для того, чтобы наблюдатель работал так, как я ожидаю. С TDD это все о создании многократно используемого кода в простом JavaScript. Есть преимущества в написании тестируемого кода на простом JavaScript. Протестируйте все и сохраните то, что хорошо для повторного использования кода.

С этим мы EventObserver . Вопрос в том, что вы можете построить с этим?

Шаблон Observer в действии: демонстрация количества слов в блоге

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

Чтобы получить количество слов при вводе свободного текста, можно сделать:

 const getWordCount = (text) => text ? text.trim().split(/\s+/).length : 0; 

Выполнено! В этой, казалось бы, простой функции происходит много всего, так как насчет скромного модульного теста? Таким образом, становится ясно, что я намеревался сделать:

 // Arrange const blogPost = 'This is a blog \n\n post with a word count. '; // Act const count = getWordCount(blogPost); // Assert assert.strictEqual(count, 9); 

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

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

Время подключить эти повторно используемые компоненты к DOM. Это та часть, где вы можете использовать простой JavaScript и встроить его прямо в браузер.

Способ сделать это будет иметь следующий HTML-код на странице:

 <textarea id="blogPost" placeholder="Enter your blog post..." class="blogPost"> </textarea> 

Вслед за этим JavaScript:

 const wordCountElement = document.createElement('p'); wordCountElement.className = 'wordCount'; wordCountElement.innerHTML = 'Word Count: <strong id="blogWordCount">0</strong>'; document.body.appendChild(wordCountElement); const blogObserver = new EventObserver(); blogObserver.subscribe((text) => { const blogCount = document.getElementById('blogWordCount'); blogCount.textContent = getWordCount(text); }); const blogPost = document.getElementById('blogPost'); blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value)); 

Возьмите весь свой повторно используемый код и поместите шаблон проектирования наблюдателя. Это позволит отслеживать изменения в текстовой области и подсчитывать количество слов прямо под ней. Я использую body.appendChild() в DOM API, чтобы добавить этот новый элемент. Затем присоединение слушателей мероприятия, чтобы воплотить его в жизнь.

Обратите внимание, что с помощью стрелок можно связать события одной строкой. Фактически, вы транслируете управляемые событиями изменения всем подписчикам с этим. () => blogObserver.broadcast() выполняет большую часть работы здесь. Он даже передает последние изменения в текстовую область прямо в функцию обратного вызова. Да, скрипты на стороне клиента — это круто.

Ни одна демонстрация не будет полной без той, которую вы можете потрогать и настроить, ниже приведен кодекс:

Теперь я бы не назвал эту функцию завершенной. Это всего лишь отправная точка модели проектирования наблюдателя. На мой взгляд, вопрос, как далеко вы готовы пойти?

Заглядывая вперед

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

Вы можете улучшить демо с:

  • Еще один компонент, который считает количество абзацев
  • Еще один компонент, который показывает предварительный просмотр введенного текста
  • Например, улучшите предварительный просмотр с поддержкой уценки

Это всего лишь несколько идей, которые вы можете сделать, чтобы глубже погрузиться в это. Вышеперечисленные усовершенствования бросят вызов вашим программистам.

Вывод

Шаблон проектирования наблюдателя может помочь вам решить реальные проблемы в JavaScript. Это решает извечную проблему синхронизации множества элементов с одними и теми же данными. Как часто бывает, когда браузер запускает определенные события. Я уверен, что большинство из вас уже столкнулись с такой проблемой и столкнулись с инструментами и сторонними зависимостями.

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

Я надеюсь, вы понимаете, как много, с небольшой дисциплиной и усилиями, вы можете сделать простым JavaScript. Новые функции языка, такие как ES6, помогут вам написать краткий код, который можно использовать повторно.

Эта статья была рецензирована Джулио Майнарди . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!