предисловие
Зачем писать эту статью?
- Я четко помню, что только что нашел работу в узле и поговорил с интервьюером о цикле событий, а затем интервьюер спросил, как произошло событие? При каких обстоятельствах генерируется событие. . .
- В каких сценариях применяются События?
- Прежде чем инкапсулировать фреймворк сетевых запросов с открытым исходным кодом RxJava, он также основан на модели публикации-подписки, и все языки одинаковы, что очень интересно. Смайлики
- Модуль Events является частью расширенного маршрута моей официальной учетной записи Node.js.
Об авторе: koala, сосредоточив внимание на совместном использовании полного стека технологий Node.js, от JavaScript до Node.js, до серверной базы данных, я желаю вам стать отличным старшим инженером Node.js. [Руководство по развитию программиста] Автор, блог Github с открытым исходным кодомGitHub.com/koala-co Nth…
интервью будет спрашивать
Расскажите мне, как Node.js относится к модели публикации/подписки
Использовался ли модуль Events при реальной разработке проекта? Каков конкретный сценарий применения?
Является ли порядок выполнения функций прослушивателя событий асинхронным или синхронным?
Назовите несколько общих функций модуля «События»?
Моделирование событий основного модуля, реализующего Node.js.
Статья впервые опубликована в блоге Github с открытым исходным кодомGitHub.com/koala-co Nth…
Шаблон публикации/подписки
发布/订阅者模式
Вероятно, самый большой шаблон дизайна, с которым я столкнулся во время разработки.发布/订阅者模式
, который также можно назвать механизмом сообщений, определяет отношение зависимости, которое можно понимать как1对N
(Примечание: не обязательно 1-ко-многим, иногда 1-к-1), наблюдатели одновременно отслеживают соответствующее изменение состояния объекта, и как только изменение будет сделано, все наблюдатели будут уведомлены, тем самым инициируя соответствующие события наблюдателей.Этот шаблон проектирования устраняет функциональные различия между объектом-субъектом и наблюдателем.耦合
.
Паттерн публикации/подписки в жизни
полиция ловит вора
В реальной жизни задержание воров полицией — это типичный режим наблюдателя: «Это пример обычного преступника, который делает покупки на улице, а потом его ловят». "Вор что-то воровал. "Просто пошлите сигнал полицейскому. На самом деле, вор не может сказать полицейскому, что я что-то украл". Полицейский получил сигнал и вышел, чтобы поймать вора. Это шаблон наблюдателя
Подписаться на газету
Жизнь подобна походу в газету, чтобы подписаться на газету. Вы идете в газету, чтобы оплатить подписку на любую газету, которую вам нравится читать. Когда выходит новая газета, газета отправляет копию всем, кто подписался на нее. газета, и подписчики могут получить.
вы подписались на мой официальный аккаунт
Автор моей общедоступной учетной записи WeChat является издателем, а вы, пользователи WeChat, являетесь подписчиками «Когда я отправляю статью, подписчики, которые следуют [Руководству по развитию программиста], могут получить статью.
Реализация кода и анализ примера
всем订阅公众号
Например, см.发布/订阅模式
как добиться. (На примере подписки на газету можно добавитьtype
Параметр используется для различения подписок на разные типы официальных учетных записей.Например, некоторые люди подписываются на официальную учетную запись внешнего интерфейса, а некоторые люди подписываются на официальную учетную запись Node.js.Этот атрибут используется для ее пометки. Это больше соответствует исходному коду EventEmitter, который будет обсуждаться далее.Еще одна причина заключается в том, что когда вы открываете статью об учетной записи подписки, вы думаете о модели публикации-подписки? )
код показывает, как показано ниже:
let officeAccounts ={
// 初始化定义一个存储类型对象
subscribes:{
'any':[]
},
// 添加订阅号
subscribe:function(type='any',fn){
if(!this.subscribes[type]){
this.subscribes[type] = [];
}
this.subscribes[type].push(fn);//将订阅方法存在数组中
},
// 退订
unSubscribe:function(type='any',fn){
this.subscribes[type] =
this.subscribes[type].filter((item)=>{
return item!=fn;// 将退订的方法从数组中移除
});
},
// 发布订阅
publish:function(type='any',...args){
this.subscribes[type].forEach(item => {
item(...args);// 根据不同的类型调用相应的方法
});
}
}
Выше приведена реализация простейшего режима наблюдателя.Вы можете видеть, что код очень прост.Основной принцип заключается в том, чтобы хранить методы подписки в массиве по категориям, а затем извлекать и выполнять их при публикации.
Далее посмотрите на код подписки Сяомина на статью [Programmer Growth Guide]:
let xiaoming = {
readArticle:function (info) {
console.log('小明收到的',info);
}
};
let xiaogang = {
readArticle:function (info) {
console.log('小刚收到的',info);
}
};
officeAccounts.subscribe('程序员成长指北',xiaoming.readArticle);
officeAccounts.subscribe('程序员成长指北',xiaogang.readArticle);
officeAccounts.subscribe('某公众号',xiaoming.readArticle);
officeAccounts.unSubscribe('某公众号',xiaoming.readArticle);
officeAccounts.publish('程序员成长指北','程序员成长指北的Node文章');
officeAccounts.publish('某公众号','某公众号的文章');
результат операции:
小明收到的 程序员成长指北的Node文章
小刚收到的 程序员成长指北的Node文章
- В заключение
Наблюдение за тремя примерами из реальной жизни и примерами кода показывает, что модель публикации/подписки действительно является отношением 1-к-N. Все подписчики уведомляются об изменении состояния издателя.
- Возможности и структура шаблона публикации/подписки Три элемента:
- диктор
- подписчик
- мероприятие (подписка)
Плюсы и минусы шаблона публикации/подписки
- преимущество
Между субъектом и наблюдателем существует полная прозрачность, и все процессы доставки сообщений осуществляются через центр диспетчеризации сообщений, то есть конкретный код бизнес-логики будет находиться в центре диспетчеризации сообщений, а полная коммуникация между субъектом и наблюдатель достигнут.Слабая связь. Объекты напрямую отделены, и в асинхронном программировании может быть написан более слабо связанный код.
- недостаток
Читабельность программы значительно снижается, когда несколько издателей и подписчиков вложены друг в друга, программу трудно отследить, да и код на самом деле не так просто читать, хе-хе.
Связь между EventEmitter и моделью публикации/подписки
EventEmitter в Node.js Модуль использует шаблон проектирования публикации/подписки. Модель публикации/подписки вводит центр отправки сообщений между субъектом и наблюдателем. Субъект и наблюдатель полностью прозрачны. Все процессы доставки сообщений выполняются через центр отправки сообщений. то есть конкретный код бизнес-логики будет завершен в центре диспетчеризации сообщений.
Основные элементы мероприятия
Сравнивая API, давайте взглянем на модуль событий.Определение EventEmitter
Events — это часто используемый модуль в Node.js, и другие нативные модули Node.js основаны на нем, например потоковая передача, HTTP и т. д. Его основная идея заключается в том, что функция модуля «События»事件绑定与触发
, все экземпляры, которые наследуются от него, могут обрабатывать события.
Сравнительное исследование некоторых часто используемых официальных исходных кодов API EventE и режима публикации/подписки.
Официальное объяснение API этого модуля не ведет вас напрямую к изучению документации, но
пройти через对比
Шаблон проектирования публикации/подписки. Напишите версию основного кода Events самостоятельно, чтобы изучить и запомнить API.
Модуль событий
Модуль Events имеет только один класс EventEmitter, сначала определите базовую структуру класса.
function EventEmitter() {
//私有属性,保存订阅方法
this._events = {};
}
//默认设置最大监听数
EventEmitter.defaultMaxListeners = 10;
module.exports = EventEmitter;
по методу
on, этот метод используется для подписки на события (здесь объясняются и addListener), исходный код Node.js назначает их обоих, я не знаю, почему? Кто знает, может сказать мне, почему я это делаю.
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
Далее идет наша конкретная практика метода on:
EventEmitter.prototype.on =
EventEmitter.prototype.addListener = function (type, listener, flag) {
//保证存在实例属性
if (!this._events) this._events = Object.create(null);
if (this._events[type]) {
if (flag) {//从头部插入
this._events[type].unshift(listener);
} else {
this._events[type].push(listener);
}
} else {
this._events[type] = [listener];
}
//绑定事件,触发newListener
if (type !== 'newListener') {
this.emit('newListener', type);
}
};
Поскольку существуют другие подклассы, которые должны наследоваться от EventEmitter, необходимо решить, имеет ли подкласс атрибут _event, чтобы убедиться, что подкласс должен иметь этот атрибут экземпляра. Флаг flag — это флаг вставки метода подписки. Если он равен true, считается, что он вставлен в начало массива. Как видите, это реализация метода подписки шаблона Observer.
метод излучения
EventEmitter.prototype.emit = function (type, ...args) {
if (this._events[type]) {
this._events[type].forEach(fn => fn.call(this, ...args));
}
};
Метод emit должен взять метод подписки и выполнить его, а также использовать метод call для исправления указания this, чтобы он указывал на экземпляр подкласса.
один раз метод
EventEmitter.prototype.once = function (type, listener) {
let _this = this;
//中间函数,在调用完之后立即删除订阅
function only() {
listener();
_this.removeListener(type, only);
}
//origin保存原回调的引用,用于remove时的判断
only.origin = listener;
this.on(type, only);
};
Метод Once очень интересен, его функция заключается в том, чтобы подписаться на событие "один раз", и когда событие сработает, оно больше не сработает. Принцип заключается в том, чтобы обернуть метод подписки другим уровнем функции и удалить эту функцию после выполнения.
метод выключения
EventEmitter.prototype.off =
EventEmitter.prototype.removeListener = function (type, listener) {
if (this._events[type]) {
//过滤掉退订的方法,从数组中移除
this._events[type] =
this._events[type].filter(fn => {
return fn !== listener && fn.origin !== listener
});
}
};
Метод off — отписаться, принцип тот же, что и в режиме наблюдателя, просто удалить метод подписки из массива.
prependListener метод
EventEmitter.prototype.prependListener = function (type, listener) {
this.on(type, listener, true);
};
Больше рассказывать об этом методе не нужно, просто вызовите метод on, чтобы передать отметку как истинную (метод подписки вставить в шапку). Выше реализованы основные методы класса EventEmitter.
Некоторые другие менее часто используемые API
-
emitter.listenerCount(eventName)
Можно получить регистрацию на мероприятиеlistener
количество -
emitter.listeners(eventName)
Можно получить регистрацию на мероприятиеlistener
Копия массива.
Небольшие упражнения после изучения API
//event.js 文件
var events = require('events');
var emitter = new events.EventEmitter();
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener1', arg1, arg2);
});
emitter.on('someEvent', function(arg1, arg2) {
console.log('listener2', arg1, arg2);
});
emitter.emit('someEvent', 'arg1 参数', 'arg2 参数');
Выполните приведенный выше код, результат выполнения следующий:
$ node event.js
listener1 arg1 参数 arg2 参数
listener2 arg1 参数 arg2 参数
Инструкции после рукописного кода
При написании кода модуля Events обратите внимание на следующие моменты:
- Используйте модель подписки/публикации
- Каковы основные компоненты мероприятия
- При написании исходного кода рассмотрите некоторые рамки и ограничьте суждения.
Примечание: Рукописный код выше не самый лучший и полный, цель просто дать всем понять и запомнить его в первую очередь. Например:
Исходное определение класса EventEmitter напрямую не определено в исходном коде.this._events = {}
, видеть:
function EventEmitter() {
EventEmitter.init.call(this);
}
EventEmitter.init = function() {
if (this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
То же самое с реализацией класса, но исходный код уделяет больше внимания производительности, мы можем подумать, что это просто.this._events = {}
; хорошо, но поjsperf
(Небольшое пасхальное яйцо, ищите ниже, если вам это нужно, проверьте инструменты производительности) Сравните производительность двух, исходный код намного выше, я не буду объяснять их по одному, и прикрепите адрес исходного кода, вы можете учись, если тебе интересно
адрес источника lib/eventsGitHub.com/node будет /node…
Исходный код слишком длинный, а адрес можно сравнить и продолжить изучение, ведь это статья в публичном аккаунте, и я не хочу, чтобы мне говорили. Но некоторые вопросы все же нужно сказать, хе-хе.
Объяснение некоторых вопросов после прочтения исходного кода
Является ли порядок выполнения функций прослушивателя синхронным или асинхронным?
Посмотрите на кусок кода:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
console.log('listener1');
});
myEmitter.on('event', async function() {
console.log('listener2');
setTimeout(() => {
console.log('我是异步中的输出');
resolve(1);
}, 1000);
});
myEmitter.on('event', function() {
console.log('listener3');
});
myEmitter.emit('event');
console.log('end');
Результат выглядит следующим образом:
// 输出结果
listener1
listener2
listener3
end
我是异步中的输出
Когда EventEmitter инициирует событие, каждый监听函数的调用
Он синхронный (примечание: вызов функции-слушателя синхронен, а вывод end находится в конце), но это не значит, что функция-слушатель не может содержать асинхронный код.Событие listener2 в коде добавляет асинхронная функция, которая является последним выходом.
При каких обстоятельствах генерируются события в цикле событий? Спровоцировано при каких обстоятельствах?
Почему я написал это отдельным подзаголовком, потому что я обнаружил, что многие статьи в Интернете неверны или неясны, вводя всех в заблуждение.
Посмотрите здесь, абзац веб-сайта API, конкретное имя веб-сайта здесь не будет упоминаться, не хочу набирать хакеров, этот контент в порядке, но легко запутать мелких партнеров, которые плохо знакомы с механизмом событий.
кfs.open
В качестве примера посмотрим, когда генерируется событие, когда оно срабатывает и какое отношение оно имеет к EventEmitter?
Описание процесса: На этом рисунке показан подробный рисунок с начала асинхронного вызова ---> инкапсуляция запроса асинхронного вызова ---> объект запроса передается в пул потоков ввода-вывода для завершения ввода-вывода. /O операция ---> ввод/вывод должен быть завершен. Результат O передается наблюдателю ввода/вывода --->Функция обратного вызова и результат вызываются и выполняются из наблюдателя ввода/вывода.
генерация событий
Про события вы видите третью часть рисунка, цикл событий там. Все асинхронные операции ввода-вывода в Node.js (net.Server, fs.readStream и т. д.)完成后
добавит событие в очередь событий цикла событий.
триггер события
Чтобы вызвать событие, нам нужно только обратить внимание на третью часть рисунка, цикл событий вынесет обработку события в очередь событий.fs.open
Все объекты, генерирующие события, являются экземплярами events.EventEmitter, унаследованными от EventEmitter.Когда событие удаляется из цикла обработки событий, запускается событие и функция обратного вызова.
Чем больше пишу, тем больше думаю об этом, это всегда так, и это нужно контролировать.
Проблема с ошибкой типа события
Когда мы определяем событие ошибки непосредственно для EventEmitter, оно содержит семантику ошибки, и мы обычно запускаем событие ошибки, когда сталкиваемся с исключением.
Когда возникает ошибка, EventEmitter указывает, что если нет отзывчивого прослушивателя, Node.js будет рассматривать это как исключение и выходить из программы с сообщением об ошибке.
var events = require('events');
var emitter = new events.EventEmitter();
emitter.emit('error');
ошибка при запуске
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: Uncaught, unspecified 'error' event.
at EventEmitter.emit (events.js:50:15)
at Object.<anonymous> (/home/byvoid/error.js:5:9)
at Module._compile (module.js:441:26)
at Object..js (module.js:459:10)
at Module.load (module.js:348:31)
at Function._load (module.js:308:12)
at Array.0 (module.js:479:10)
at EventEmitter._tickCallback (node.js:192:40)
Как правило, нам нужно настроить прослушиватели для объектов, которые будут вызывать событие ошибки, чтобы избежать сбоя всей программы после обнаружения ошибки.
Как изменить максимальное количество слушателей для EventEmitter?
По умолчанию максимальное количество прослушивателей для одного события равно 10. Если прослушивателей более 10, прослушиватель все равно будет выполняться, но на консоли появится предупреждающее сообщение. можно настроить, вызвав emitter.setMaxListeners().
(node:9379) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit
Небольшой трюк, чтобы напечатать детали предупреждения
Детализации приведенной выше информации о предупреждении недостаточно, и она не может сказать нам, где код неверен.Вы можете получить более конкретную информацию (эмиттер, событие, eventCount) через process.on('warning')
process.on('warning', (e) => {
console.log(e);
})
{ MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit
at _addListener (events.js:289:19)
at MyEmitter.prependListener (events.js:313:14)
at Object.<anonymous> (/Users/xiji/workspace/learn/event-emitter/b.js:34:11)
at Module._compile (module.js:641:30)
at Object.Module._extensions..js (module.js:652:10)
at Module.load (module.js:560:32)
at tryModuleLoad (module.js:503:12)
at Function.Module._load (module.js:495:3)
at Function.Module.runMain (module.js:682:10)
at startup (bootstrap_node.js:191:16)
name: 'MaxListenersExceededWarning',
emitter:
MyEmitter {
domain: null,
_events: { event: [Array] },
_eventsCount: 1,
_maxListeners: undefined },
type: 'event',
count: 11 }
Сценарии применения EventEmitter
- Не могу попробовать/поймать исключение ошибки, можно использовать его
- Многие распространенные модули наследуются от EventEmitter.
Например
fs
модульnet
модуль - вопросы интервью
- Режим публикации/подписки также часто используется во фронтенд-разработке (идея та же, что и у модуля Events).
Небольшое объяснение шаблона публикации/подписки и шаблона наблюдателя
Режим наблюдателя и режим публикации-подписки, вы можете думать о них как об одном в обычное время, но в некоторых случаях (например, во время интервью) вам может потребоваться уделить немного внимания, посмотрите на разницу между ними.
Заимствуйте картинку из интернета
Как видно из рисунка, модель публикации-подписки содержит канал событий посередине.- Между наблюдателем и наблюдаемым в шаблоне наблюдателя по-прежнему существует связь, и оба должны знать о существовании другого, прежде чем они смогут передавать сообщения.
- Издателям и подписчикам в модели «публикация-подписка» не нужно знать о существовании друг друга, они общаются через брокера сообщений, и разделение происходит более тщательно.
Справочная статья:
- Официальный сайт Node.js
- Node.js г-на Пак Линга объясняется простым языком
- Исходный адрес событий в githubGitHub.com/node будет /node…
- Шаблоны проектирования JavaScript - SHERlocked93
Присоединяйтесь к нам, чтобы учиться вместе!