Skip to content

Commit 56f070e

Browse files
authored
Merge pull request #20 from JavaScript-Basic-OTUS/new-lectures
New lectures
2 parents 7dac46d + 23644f4 commit 56f070e

File tree

21 files changed

+2080
-3
lines changed

21 files changed

+2080
-3
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"responsive-preview": {
3+
"Mobile": [320, 675],
4+
"Tablet": [1024, 765],
5+
"Desktop": [1400, 800],
6+
"Desktop HD": [1920, 1080]
7+
}
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<html>
2+
<head>
3+
<title>Parcel Sandbox</title>
4+
<meta charset="UTF-8" />
5+
</head>
6+
7+
<body>
8+
<h1>Event Emitter / Event Bus</h1>
9+
<div id="app"></div>
10+
11+
<script src="src/index.ts"></script>
12+
</body>
13+
</html>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "event-emitter",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.html",
6+
"scripts": {
7+
"start": "parcel index.html --open",
8+
"build": "parcel build index.html"
9+
},
10+
"dependencies": {
11+
"@types/jest": "26.0.21",
12+
"parcel-bundler": "^1.6.1"
13+
},
14+
"devDependencies": {
15+
"typescript": "4.2.3"
16+
},
17+
"resolutions": {
18+
"@babel/preset-env": "7.13.8"
19+
},
20+
"keywords": []
21+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { EventEmitter } from "./EventEmitter";
2+
3+
describe("EventEmitter", () => {
4+
describe("formal public interface", () => {
5+
it("is a constructor", () => {
6+
expect(typeof EventEmitter).toBe("function");
7+
expect(new EventEmitter() instanceof EventEmitter).toBe(true);
8+
});
9+
10+
it("has public methods", () => {
11+
const eventEmitter = new EventEmitter();
12+
13+
expect(typeof eventEmitter.on).toBe("function");
14+
expect(typeof eventEmitter.off).toBe("function");
15+
expect(typeof eventEmitter.trigger).toBe("function");
16+
});
17+
});
18+
19+
describe("runtime logic", () => {
20+
let eventEmitter: EventEmitter;
21+
const eventName = "eventName";
22+
const eventPayload = { name: "Bob" };
23+
24+
beforeEach(() => {
25+
eventEmitter = new EventEmitter();
26+
});
27+
it("allows to listen to the events", () => {
28+
const spy = jest.fn();
29+
eventEmitter.on(eventName, spy);
30+
expect(spy).not.toHaveBeenCalled();
31+
eventEmitter.trigger(eventName, eventPayload);
32+
expect(spy).toHaveBeenCalledWith(eventPayload);
33+
});
34+
35+
it("supports multiple listeners for the event", () => {
36+
const spy1 = jest.fn();
37+
const spy2 = jest.fn();
38+
eventEmitter.on(eventName, spy1);
39+
eventEmitter.on(eventName, spy2);
40+
expect(spy1).not.toHaveBeenCalled();
41+
expect(spy2).not.toHaveBeenCalled();
42+
eventEmitter.trigger(eventName, eventPayload);
43+
expect(spy1).toHaveBeenCalledWith(eventPayload);
44+
expect(spy2).toHaveBeenCalledWith(eventPayload);
45+
});
46+
47+
it("allows to unsubscribe from the events", () => {
48+
const spy1 = jest.fn();
49+
const spy2 = jest.fn();
50+
eventEmitter.on(eventName, spy1);
51+
eventEmitter.on(eventName, spy2);
52+
eventEmitter.off(eventName, spy1);
53+
eventEmitter.trigger(eventName, eventPayload);
54+
expect(spy1).not.toHaveBeenCalled();
55+
expect(spy2).toHaveBeenCalledWith(eventPayload);
56+
});
57+
58+
describe("edge cases", () => {
59+
it("handles events with no listeners", () => {
60+
expect(() => {
61+
eventEmitter.trigger(eventName, eventPayload);
62+
}).not.toThrowError();
63+
});
64+
65+
it("handles invalid unsubscriptions", () => {
66+
eventEmitter.on("x", jest.fn());
67+
expect(() => {
68+
eventEmitter.off("x", () => {});
69+
eventEmitter.off(eventName, () => {});
70+
}).not.toThrowError();
71+
});
72+
73+
it("handles triggers with no subscriptions", () => {
74+
expect(() => {
75+
eventEmitter.trigger(eventName, eventPayload);
76+
}).not.toThrowError();
77+
});
78+
});
79+
});
80+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export class EventEmitter {
2+
// @todo: put your code here
3+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// import { EventEmitter } from "./EventEmitter";
2+
3+
// (document.querySelector("#app") as HTMLElement).innerHTML = `
4+
// <input name="input1" placeholder="Enter some text..." />
5+
// <h1></h1>
6+
// <input name="input2" placeholder="Enter some text..." />
7+
// `;
8+
// const input1 = document.querySelector("input[name=input1") as HTMLInputElement;
9+
// const input2 = document.querySelector("input[name=input2") as HTMLInputElement;
10+
// const header = document.querySelector("h1") as HTMLHeadingElement;
11+
12+
// const eventEmitter = new EventEmitter();
13+
14+
// eventEmitter.on("changeText", (text) => (header.innerHTML = text));
15+
16+
// input1.addEventListener("keypress", (ev) =>
17+
// eventEmitter.trigger("changeText", (ev.target as HTMLInputElement).value)
18+
// );
19+
// input2.addEventListener("keypress", (ev) =>
20+
// eventEmitter.trigger("changeText", (ev.target as HTMLInputElement).value)
21+
// );
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"module": "commonjs",
5+
"jsx": "preserve",
6+
"esModuleInterop": true,
7+
"sourceMap": true,
8+
"allowJs": true,
9+
"lib": ["es6", "dom"],
10+
"rootDir": "src",
11+
"moduleResolution": "node"
12+
}
13+
}
17 KB
Loading
27.9 KB
Loading

lessons/lesson25/lesson.md

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,225 @@
1-
# Lesson 25
1+
---
2+
title: Занятие 25
3+
description: Связь модулей - от интерфейсов до EventBus
4+
---
5+
6+
# OTUS
7+
8+
## Javascript Basic
9+
10+
<!-- v -->
11+
12+
## Вопросы?
13+
14+
<!-- s -->
15+
16+
## Связь модулей - от интерфейсов до EventBus
17+
18+
<!-- s -->
19+
20+
### Разберемся с задачей
21+
22+
<!-- v -->
23+
24+
Для начала два термина - **связность(_cohesion_)** и **связанность(_coupling_)**.
25+
26+
[Связность](<https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D1%8F%D0%B7%D0%BD%D0%BE%D1%81%D1%82%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)>) - на сколько составные части направлены на решение одной задачи.
27+
28+
[Связанность](<https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D1%86%D0%B5%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)>) - на сколько одни модули зависят от других (и как много они знают друг о друге)
29+
30+
<!-- v -->
31+
32+
[Качественный дизайн обладает слабой связанностью (low coupling) и сильной связностью (high cohesion).](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9)
33+
34+
Это значит, что программный компонент имеет небольшое число внешних связей и отвечает за решение близких по смыслу задач.
35+
36+
<!-- v -->
37+
38+
**Слабое зацепление (Low Coupling)** и **Высокая связность (High Cohesion)** это 2 из 9 [**шаблонов GRASP**](<https://ru.wikipedia.org/wiki/GRASP#4._%D0%A1%D0%BB%D0%B0%D0%B1%D0%BE%D0%B5_%D0%B7%D0%B0%D1%86%D0%B5%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_(Low_Coupling)>)
39+
40+
<!-- v -->
41+
42+
Высокая связность говорит об эффективности программы (или ее отдельных модулей).
43+
44+
Низкая связанность означает легкость рефакторинга и переиспользуемость кода.
45+
46+
<!-- v -->
47+
48+
### Вопросы?
49+
50+
<!-- s -->
51+
52+
### Наблюдатель (Observer)
53+
54+
<!-- v -->
55+
56+
[Наблюдатель](https://refactoring.guru/ru/design-patterns/observer) - подход (паттерн), позволяющий одним объектам следить и реагировать на события, происходящие в других объектах.
57+
58+
<!-- v -->
59+
60+
<img src="./images/ObservableUML.png" title="Observable UML" />
61+
62+
<!-- v -->
63+
64+
На самом деле вы с ним уже работали - это [EventTarget](https://developer.mozilla.org/ru/docs/Web/API/EventTarget)
65+
66+
<!-- v -->
67+
68+
Данный шаблон часто применяют в ситуациях, в которых отправителя сообщений не интересует, что делают получатели с предоставленной им информацией.
69+
70+
<!-- v -->
71+
72+
Может быть представлен как
73+
74+
<!-- eslint-skip -->
75+
76+
```ts
77+
IObservable {
78+
addObserver(event, handler)
79+
removeObserver(event, handler)
80+
notifyObserver(event, data)
81+
}
82+
```
83+
84+
<!-- v -->
85+
86+
или
87+
88+
<!-- eslint-skip -->
89+
90+
```ts
91+
EventTarget {
92+
addEventListener(event, handler)
93+
removeEventListener(event, handler)
94+
dispatchEvent(event)
95+
}
96+
```
97+
98+
<!-- v -->
99+
100+
или
101+
102+
<!-- eslint-skip -->
103+
104+
```ts
105+
Backbone.Events {
106+
on(event, handler)
107+
off(event, handler)
108+
trigger(event)
109+
}
110+
```
111+
112+
<!-- v -->
113+
114+
Иногда могут добавлять вспомогательные методы, например
115+
116+
<!-- eslint-skip -->
117+
118+
```ts
119+
Backbone.Events {
120+
// ...
121+
once(event, handler)
122+
}
123+
```
124+
125+
<!-- v -->
126+
127+
```ts
128+
document.querySelector(element).addEventListener("click", (ev) => {
129+
alert("Boom!");
130+
});
131+
```
132+
133+
<!-- v -->
134+
135+
Оговорка: чаще всего обработчиком события является функция. Но это также может быть и объект ([EventListener](https://developer.mozilla.org/ru/docs/Web/API/EventListener)) - в зависимости от реализации.
136+
137+
<!-- v -->
138+
139+
### Вопросы?
140+
141+
<!-- s -->
142+
143+
### Посредник (Mediator)
144+
145+
<!-- v -->
146+
147+
[Посредник](https://refactoring.guru/ru/design-patterns/mediator) - это поведенческий паттерн проектирования, который позволяет уменьшить связанность множества классов между собой, благодаря перемещению этих связей в один класс-посредник.
148+
149+
<!-- v -->
150+
151+
**Задача:** Обеспечить взаимодействие множества объектов, сформировав при этом слабую связанность и избавив объекты от необходимости явно ссылаться друг на друга.
152+
153+
**Решение:** Создать объект, инкапсулирующий способ взаимодействия множества объектов.
154+
155+
**Преимущества:** Устраняется связанность между "Коллегами", централизуется управление.
156+
157+
<!-- v -->
158+
159+
Самый распространенный (и простой) вариант реализации паттерна - с использованием **EventEmitter** интерфейса (**Event Bus** - Шина событий).
160+
161+
<!-- v -->
162+
163+
Разница, по сравнению с обычным использованием EventTarget:
164+
165+
- события в EventTarget генерирует сам объект, при работе с EventBus это делают сторонние объекты
166+
- список событий при работе с EventTarget ограничен устройством объекта, при работе с EventBus он определяется участниками
167+
168+
<!-- v -->
169+
170+
<img src="./images/EventBus.jpeg" title="Event Bus" />
171+
172+
<!-- v -->
173+
174+
При этом, чтобы избежать коллизии имен событий, зачастую вводят `namespaces`, в формате **{NAMESPACE}:{EVENT NAME}**. Например `user:add`, `searchHistory:add`.
175+
176+
Нужно отметить, что по-хорошему, префиксы делаются на основе сущностей, а не на основе модулей (иначе происходит раскрытие структуры системы).
177+
178+
<!-- v -->
179+
180+
```ts
181+
const eventBus = new EventBus();
182+
183+
// module 1
184+
eventBus.on("city:changed", (cityName) => console.log(`New city: ${cityName}`));
185+
186+
// module 2
187+
eventBus.trigger("city:changed", "Minsk");
188+
```
189+
190+
<!-- v -->
191+
192+
Как мы могли бы применить это к уже сделанным домашним заданиям?
193+
194+
<!-- v -->
195+
196+
### Вопросы?
197+
198+
<!-- s -->
199+
200+
### Практика
201+
202+
<!-- v -->
203+
204+
[Реализовать Event Emitter](https://codesandbox.io/s/github/vvscode/otus--javascript-basic/tree/master/lessons/lesson33/code/eventEmitter)
205+
206+
<!-- v -->
207+
208+
Реализовать поверх существующего функционала метод **once** (для одноразового вызова обработчика).
209+
210+
<!-- v -->
211+
212+
### Вопросы?
213+
214+
<!-- s -->
215+
216+
Дополнительные материалы:
217+
218+
- [Backbone Events](https://backbonejs.org/#Events) и [исходники](https://backbonejs.org/docs/backbone.html#section-17)
219+
- [EventTarget simple implementation](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
220+
- [Паттерны проектирования понятным языком](https://refactoring.guru/ru/design-patterns)
221+
- [Design patterns for humans!](https://github.com/sohamkamani/javascript-design-patterns-for-humans)
222+
223+
<!-- v -->
224+
225+
### Опрос о занятии

0 commit comments

Comments
 (0)