От режима наблюдателя к рукописному исходному коду EventEmitter

Node.js внешний интерфейс Шаблоны проектирования JavaScript
От режима наблюдателя к рукописному исходному коду EventEmitter

Шаблон наблюдателя

Паттерн Observer (наблюдатель) широко используется в языке javascript, события браузера (такие как щелчок мышью, событие клавиатуры keyDown) являются примерами этого паттерна. Основная причина разработки этого шаблона заключается в том, чтобы способствовать формированию низкой связанности, в этом шаблоне не просто объект вызывает объект, а один объект «подписывается» на активность другого объекта, когда состояние активности объекта изменения, просто уведомить подписчиков, а подписчиков еще называют наблюдателями.

Подписка на газету

Жизнь подобна походу в газету, чтобы подписаться на газету. Вы идете в газету, чтобы оплатить подписку на любую газету, которую вам нравится читать. Когда выходит новая газета, газета отправляет копию всем, кто подписался на нее. газета, и подписчики могут получить.

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

Джек должен иметь свойство subscribers, которое является типом массива, поведение подписки будет храниться в этом массиве по порядку, а под уведомлением подразумевается вызов метода объекта-подписчика. Поэтому, когда пользователь Том подписывается на сообщение, подписчик предоставляет один из своих методов для подписки () Джека. Конечно, вы также можете отказаться от подписки.Я больше не хочу читать газету, поэтому я вызываю функцию unsubscribe(), чтобы отменить подписку.

Простой шаблон наблюдателя должен иметь следующие члены:

  • подписывается на массив
  • subscribe() добавляет подписку в массив
  • unsubscribe() удаляет подписку из массива
  • publish() перебирает массив и вызывает метод подписки.

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

Делаем это простым кодом:

var Jack = {
    subscribers: {
        'any': []
    },
	//添加订阅
    subscribe: function (type = 'any', fn) {
        if (!this.subscribers[type]) {
            this.subscribers[type] = [];
        }
        this.subscribers[type].push(fn); //将订阅方法保存在数组里
    },
	//退订
    unsubscribe: function (type = 'any', fn) {
        this.subscribers[type] =
            this.subscribers[type].filter(function (item) { 
                return item !== fn;
            }); //将退订的方法从数组中移除
    },
	//发布订阅
    publish: function (type = 'any', ...args) {
        this.subscribers[type].forEach(function (item) { 
            item(...args);	//根据不同的类型调用相应的方法
        });
    }
};

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

Используйте Тома, чтобы подписаться ниже:

var Tom = {
    readNews: function (info) {
        console.log(info);
    }
};

//Tom订阅Jack的报纸
Jack.subscribe('娱乐', Tom.readNews);
Jack.subscribe('体育', Tom.readNews);

//Tom 退订娱乐新闻:
Jack.unsubscribe('娱乐', Tom.readNews);

//发布新报纸:
Jack.publish('娱乐', 'S.H.E演唱会惊喜登台')
Jack.publish('体育', '欧国联-意大利0-1客负葡萄牙');

результат операции:

欧国联-意大利0-1客负葡萄牙

Практическое применение паттерна Observer

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

модуль событий

События node.js — это модуль с высокой частотой использования.На нем основаны другие нативные модули node.js, такие как потоковая передача, HTTP и т. д. Мы можем написать версию основного кода событий, чтобы увидеть режим наблюдателя. практическое применение.

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

function EventEmitter() {
    //私有属性,保存订阅方法
    this._events = {};
}

//默认最大监听数
EventEmitter.defaultMaxListeners = 10;

module.exports = EventEmitter;

Давайте реализуем основные методы событий один за другим.

по методу

Первый — это метод on, который используется для подписки на события, в более старых версиях node.js — это метод addListener, это одна и та же функция:

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.

резюме

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

Некоторые примеры относятся к "JavaScript Patterns" Автор: Стоян Стефанов