Observer js что это

Обновлено: 17.06.2024

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

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

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

Я буду использовать ECMAScript 6 для создания примеров иллюстрации шаблона. Еще будут классы, стрелочные функции и константы. Не стесняйтесь изучить эти темы самостоятельно, если вы еще с ними не знакомы. Так же я буду следовать Test-Driven-Development (TDD) походу. Таким образом, у нас будет способ узнать, насколько полезен каждый компонент.

Дальнейшее развитие

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

Вы можете добавим в демо:

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

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

Примеры использования наблюдателей в JavaScript


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

В JavaScript существует три основных вида наблюдателей:

  1. ResizeObserver
  2. IntersectionObserver
  3. MutationObserver

Resize Observer

Назначение

Наблюдение за изменением размеров целевого элемента.

Теория
Поддержка


Пример

В следующем примере мы наблюдаем за шириной контейнера с идентификатором «box». При ширине контейнера, меньшей 768px, мы меняем цвет фона контейнера и цвет текста (на противоположные с помощью «filter: invert(100%)»), уменьшаем размер шрифта заголовка и основного текста, а также скрываем дополнительную информацию.

Разметка выглядит следующим образом:

IntersectionObserver

Назначение

Наблюдение за пересечением целевого элемента с вышестоящим элементом или областью просмотра страницы (viewport).

Теория
Поддержка


Пример

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

MutationObserver

Назначение

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

Заключение

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

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

Mediator

На основе pub-sub строится работа паттерна Mediator, который позволяет наладить коммуникацию между различными компонентами системы. Mediator представляет собой глобальный объект в системе, о котором знают все компоненты системы, при этом компонент может выступать как слушателем события, так и издателем другого события, таким образом налаживая коммуникацию между объектами системы.

Если провести аналогию, то Mediator это городская АТС, в которую приходят входящие и исходящие вызовы от абонентов, а доходят они строго до нужного абонента. Но как мы знаем у телефонной сети есть недостаток — на новый год она может оказаться перегруженной огромным количеством звонков и перестать доставлять вызова абонентам. Тоже самое может произойти и с Mediator, когда он не справится с потоком событий.

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

Mediator паттерн довольно успешно реализован в Backbone — сам глобальный объект Backbone можно использовать в качестве Mediator, либо унаследоваться от Backbone.Events.

Используем паттерн Наблюдатель(Observer) для создания индикатора выполнения процесса на Javascript

Идея паттерна Observer заключается в создании зависимости типа один ко многим. При изменении состояния одного объекта(субъекта), зависящие от него объекты(наблюдатели) об этом оповещаются и обновляются. Это нужно для согласования состояния взаимосвязанных объектов без их жесткой связанности.


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

Паттерн наблюдатель описывает, как реализовать такое отношение. В основе лежат объекты Subject и Observer. Субъект изменяется и уведомляет о своих изменениях зависимым от него Наблюдателям. Наблюдатели синхронизируют свои данных с Субъектом. Также это отношение называют издатель-подписчик. Субъект(издатель) рассылает уведомления своим наблюдателям(подписчикам), даже не зная о том, какие объекты ими являются. При этом количество подписчиков не ограничено.

Как это все будет выглядеть в Javascript?

var Microsoft = new Publisher;
var Google = new Publisher;
var Apple = new Publisher;

* This source code was highlighted with Source Code Highlighter .

var Ann = function (from) console.log( 'Delivery from ' +from+ ' to Ann' );
>;
var Vasya = function (from) console.log( 'Delivery from ' +from+ ' to Vasya' );
>;
var Maria = function (from) console.log( 'Delivery from ' +from+ ' to Maria ' );
>;

* This source code was highlighted with Source Code Highlighter .

Ann.subscribe(Microsoft).subscribe(Google).subscribe(Apple);
Vasya.subscribe(Google).subscribe(Apple);
Maria.subscribe(Microsoft);

* This source code was highlighted with Source Code Highlighter .

Microsoft.deliver( 'news 1' ).deliver( 'news 2' );
Google.deliver( 'googlenews 1' ).deliver( 'googlenews 2' );

* This source code was highlighted with Source Code Highlighter .

Итак, теперь напишем конструктор для издателя. Внутри него хранятся подписчики.

* This source code was highlighted with Source Code Highlighter .

Publisher.prototype.deliver = function (data)

* This source code was highlighted with Source Code Highlighter .

И добавим в Function метод subscribe для добавления подписчика.

Function.prototype.subscribe = function (publisher) publisher.subscribers.push( this );
return this ;
>;

* This source code was highlighted with Source Code Highlighter .

Ну вот. Паттерн готов.

Как это все использовать?

Я покажу как использовать данный паттерн для индикатора процесса длительного действия. Этим действием может быть обработка большого массива данных, загрузка файла на сервер, длительное передвижение какого-либо персонажа и, может когда-нибудь возможно будет во всех браузерах, обработка XHR до наступления readyState=4.

У меня будет передвигается панда по тропинке Сразу пример.

var result = (to - that.from) * progress + that.from;
that.domElement.css( 'left' , result+ 'px' );
that.onGo.deliver(progress);
if (progress < 1)
setTimeout(arguments.callee, 10);
>, 10);
>

.
var Panda= new Animal( 'panda' );

* This source code was highlighted with Source Code Highlighter .

В конструкторе мы создали Издателя this.onGo=new Publisher(), а в методе сделали рассылку с текущим прогрессом that.onGo.deliver(progress).

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

var Bar= function (progress)

>

* This source code was highlighted with Source Code Highlighter .

И подпишем их на рассылку onGo


* This source code was highlighted with Source Code Highlighter .

Теперь при движении панды изменяются и индикаторы.

В итоге мы получили три независимых объекта Bar, Status и Animal, которые можно использовать независимо друг от друга, а также возможность спокойно добавлять новые объекты, зависящие от состояния панды(издателя).

Наблюдатель за событиями

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

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

Создадим и инициализируем EventObserver:

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

Метод Subscribe

Добавим новый метод subscribe:

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

Один из способов проверить этот метод:

Тут я использую функцию Node assert для тестирования этого компонента. Точно такие же assertions существуют и в Chai assertions.

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

Метод Unsubscribe

Сделаем следующий метод для удаления события:

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

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

Метод Broadcast

Следующий метод для вызова всех событий:

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

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

Проверим метод broadcast:

Этот тест позволяет мне убедиться, в том что наблюдатель работает так, как я ожидаю.

Таким образом мы создали EventObserver. Вопрос в том, что нам с ним делать дальше?

Observer vs Pub-Sub

Observer и Pub-sub, наверное самые известные паттерны взаимодействия в мире разработки интерфейсов и JavaScript. Но несмотря на свою известность, некоторые разработчики считают эти паттерны одинаковыми, что и послужило подспорьем написать данную статью.


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

Но в мире интерфейсов и асинхронного JavaScript не возможно прожить без знания паттернов observer и pub-sub, так как знание и понимание этих шаблонов, значительно облегчает жизнь разработчику.

Observer и Pub-sub относятся к поведенческим паттернам (паттернам взаимодействия), т.е. применяются, когда требуется организовать взаимодействие между различными объектами системы.

Observer

Observer представляет собой не что иное, как связь один-ко-многим. В упрощенном виде этот паттерн состоит из объекта наблюдения (subject) и наблюдателей (observers).

Принципиальная схема взаимодействия выглядит так:

Subject — реализует методы: observe, detach, notify, get, set.

Observer — реализует метод update.

Также subject содержит ссылки на всех observers, которые его слушают, а observer, в свою очередь содержит ссылку, на subject, на который он подписан.

Таким образом в этом паттерне наблюдается прямая связь между объектами, т.е. subject знает о всех своих observers и вручную оповещает их о происходящих в себе изменениях, вызывая метод update у каждого observer. Связь устанавливается методом observe, разрывается методом detach.

Subject хранит внутри себя свое состояние и все действия с его состоянием необходимо совершать используя get/set методы, чтобы при изменениях состояния вызывать метод notify. Подобная схема реализована в EmberJs.


Observer можно представить довольно неплохой картинкой (заимствовано тут):

Примерную реализацию можно найти на сайте Addy Osmani.

Pub-sub

Pub-sub паттерн является одной из вариаций паттерна Observer. Исходя из названия в паттерне выделяют два компонента Publisher (издатель) и Subscriber (подписчик). В отличие от Observer, связь между объектами осуществляется посредством канала связи Event Channel (шины событий).

Publisher кидает свои события в Event Channel, а Subscriber подписывается на нужное событие и слушает его на шине, что обеспечивает отсутствие прямой связи между подписчиком и издателем.


Схематично Pub-sub и отличие от Observer, можно представить так:

  1. отсутствие прямой связи между объектами
  2. объекты сигнализируют друг другу событиями, а не состояниями объекта
  3. возможность подписываться на различные события на одном объекте с различными обработчиками

Одной из наиболее известных реализаций паттерна pub-sub является Backbone, AmplifyJs и др. DOM, в некоторой степени тоже реализует модель pub-sub.

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

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

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

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

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

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

Добавим следующий HTML-код на страницу:

Вслед за ним JavaScript:

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

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

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

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

Что? Где? Когда?

Когда и где следует применять каждый паттерн — дело каждого, но прежде чем применять, следует понять их отличия и особенности. Например, Observer паттерн представляет собой прямую связь между объектами и сигнализирует наблюдателю об изменении своего состояния. На мой взгляд, данный паттерн очень хорошо подходит при разработке различных форм с множеством полей ввода, когда необходимо реагировать на изменения значений полей формы (binding).

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

Читайте также: