Краткий рассказ о шаблонах «публикация-подписка» и «наблюдатель» из вопроса интервью.

JavaScript
Краткий рассказ о шаблонах «публикация-подписка» и «наблюдатель» из вопроса интервью.

Сегодняшняя темаjavascript«Модель публикации-подписки и модель наблюдателя», которые часто упоминаются в этой статье, не могут не думать об интервью. Я помню, во время собеседования в прошлом году интервьюер спросил меня: «Как вы справляетесь с общением между компонентами, не являющимися родительскими и дочерними, в вашем проекте?». Я ответил: «Полезноvuex, в некоторых сценариях также будет использоватьсяEventEmitter2Интервьюер продолжал спрашивать: «Тогда вы можете написать код для реализации простогоEventEmitter? "

Рукописный EventEmitter

Я некоторое время колебался, думая об использованииEventEmitter2, в основном с использованиемemitсобытие, сonпрослушивание событий иoffуничтожить прослушиватели событий,removeAllListenersУничтожить всех слушателей назначенного события иonceтакие методы, как. Учитывая временные отношения, я думал сначала реализовать функции отправки событий, мониторинга событий и удаления слушателей. Возможно, в то время я немного нервничал, но опасности не было.После того, как интервьюер дал небольшую подсказку, я написал это гладко! Теперь запишите и эту часть кода.

class EventEmitter {
    constructor() {
        // 维护事件及监听者
        this.listeners = {}
    }
    /**
     * 注册事件监听者
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    on(type, cb) {
        if (!this.listeners[type]) {
            this.listeners[type] = []
        }
        this.listeners[type].push(cb)
    }
    /**
     * 发布事件
     * @param {String} type 事件类型
     * @param  {...any} args 参数列表,把emit传递的参数赋给回调函数
     */
    emit(type, ...args) {
        if (this.listeners[type]) {
            this.listeners[type].forEach(cb => {
                cb(...args)
            })
        }
    }
    /**
     * 移除某个事件的一个监听者
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    off(type, cb) {
        if (this.listeners[type]) {
            const targetIndex = this.listeners[type].findIndex(item => item === cb)
            if (targetIndex !== -1) {
                this.listeners[type].splice(targetIndex, 1)
            }
            if (this.listeners[type].length === 0) {
                delete this.listeners[type]
            }
        }
    }
    /**
     * 移除某个事件的所有监听者
     * @param {String} type 事件类型
     */
    offAll(type) {
        if (this.listeners[type]) {
            delete this.listeners[type]
        }
    }
}
// 创建事件管理器实例
const ee = new EventEmitter()
// 注册一个chifan事件监听者
ee.on('chifan', function() { console.log('吃饭了,我们走!') })
// 发布事件chifan
ee.emit('chifan')
// 也可以emit传递参数
ee.on('chifan', function(address, food) { console.log(`吃饭了,我们去${address}吃${food}!`) })
ee.emit('chifan', '三食堂', '铁板饭') // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者

// 测试移除事件监听
const toBeRemovedListener = function() { console.log('我是一个可以被移除的监听者') }
ee.on('testoff', toBeRemovedListener)
ee.emit('testoff')
ee.off('testoff', toBeRemovedListener)
ee.emit('testoff') // 此时事件监听已经被移除,不会再有console.log打印出来了

// 测试移除chifan的所有事件监听
ee.offAll('chifan')
console.log(ee) // 此时可以看到ee.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了

С этой простой версией, написанной мнойEventEmitter, нам не нужно полагаться на сторонние библиотеки. правильный,vueМожет также помочь нам сделать это.

const ee = new Vue();
ee.$on('chifan', function(address, food) { console.log(`吃饭了,我们去${address}吃${food}!`) })
ee.$emit('chifan', '三食堂', '铁板饭')

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

модель публикации-подписки

На самом деле, смотрите внимательно.EventEmitterЭто типичная модель публикации-подписки, реализующая центр диспетчеризации событий. В модели публикации-подписки есть три роли: издатель, центр диспетчеризации событий и подписчик. чего мы только что достиглиEventEmitterэкземплярeeЭто центр диспетчеризации событий, издатели и подписчики слабо связаны и не заботятся о существовании друг друга, они сосредоточены на самом событии. Издатель заимствует предоставленный центром диспетчеризации событийemitметод публикует события, а подписчики проходятonПодписаться.

Если непонятно, можем ли мы заменить код словами, чтобы его было легче понять?

class PubSub {
    constructor() {
        // 维护事件及订阅行为
        this.events = {}
    }
    /**
     * 注册事件订阅行为
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    subscribe(type, cb) {
        if (!this.events[type]) {
            this.events[type] = []
        }
        this.events[type].push(cb)
    }
    /**
     * 发布事件
     * @param {String} type 事件类型
     * @param  {...any} args 参数列表
     */
    publish(type, ...args) {
        if (this.events[type]) {
            this.events[type].forEach(cb => {
                cb(...args)
            })
        }
    }
    /**
     * 移除某个事件的一个订阅行为
     * @param {String} type 事件类型
     * @param {Function} cb 回调函数
     */
    unsubscribe(type, cb) {
        if (this.events[type]) {
            const targetIndex = this.events[type].findIndex(item => item === cb)
            if (targetIndex !== -1) {
                this.events[type].splice(targetIndex, 1)
            }
            if (this.events[type].length === 0) {
                delete this.events[type]
            }
        }
    }
    /**
     * 移除某个事件的所有订阅行为
     * @param {String} type 事件类型
     */
    unsubscribeAll(type) {
        if (this.events[type]) {
            delete this.events[type]
        }
    }
}

анализ чертежей

Наконец, мы рисуем картинку, чтобы углубить наше понимание:

发布订阅模式图解

Функции

  • В модели публикации-подписки для издателяPublisherи подписчикиSubscriberБез особых ограничений они кажутся анонимными действиями, публикацией и подпиской на события через интерфейс, предоставляемый центром диспетчеризации событий, без знания того, кто является другой стороной.
  • Слабосвязанный, очень гибкий, часто используется в качестве шины событий.
  • понятный, сравнимый сDOMв случаеdispatchEventа такжеaddEventListener.

недостаток

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

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

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

  • Наблюдатель долженObserverдостигатьupdateметод для вызова целевого объекта.updateПользовательский бизнес-код может быть выполнен в методе.
  • цельSubjectЕго также обычно называют наблюдаемым объектом или субъектом, его функция очень едина, можно понять, что он управляет только одним видом событий.SubjectНеобходимо поддерживать собственный массив наблюдателейobserverList, когда сам изменяется, вызывая свой собственныйnotifyметод, который, в свою очередь, уведомляет каждого наблюдателя о выполненииupdateметод.

Следуя этому определению, мы можем реализовать простую версию шаблона Observer.

// 观察者
class Observer {
    /**
     * 构造器
     * @param {Function} cb 回调函数,收到目标对象通知时执行
     */
    constructor(cb){
        if (typeof cb === 'function') {
            this.cb = cb
        } else {
            throw new Error('Observer构造器必须传入函数类型!')
        }
    }
    /**
     * 被目标对象通知时执行
     */
    update() {
        this.cb()
    }
}

// 目标对象
class Subject {
    constructor() {
        // 维护观察者列表
        this.observerList = []
    }
    /**
     * 添加一个观察者
     * @param {Observer} observer Observer实例
     */
    addObserver(observer) {
        this.observerList.push(observer)
    }
    /**
     * 通知所有的观察者
     */
    notify() {
        this.observerList.forEach(observer => {
            observer.update()
        })
    }
}

const observerCallback = function() {
    console.log('我被通知了')
}
const observer = new Observer(observerCallback)

const subject = new Subject();
subject.addObserver(observer);
subject.notify();

анализ чертежей

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

观察者模式

Функции

  • Роль предельно ясна, нет центра диспетчеризации событий как посредника, целевой объектSubjectи наблюдательObserverОба реализуют контрактные методы членов.
  • Две стороны более тесно связаны, и целевой объект очень активен, сам собирает и поддерживает наблюдателей и активно уведомляет наблюдателей об обновлении при изменении состояния.

недостаток

Я еще не понял, поэтому не буду комментировать здесь

Эпилог

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

Мой уровень ограничен, вышесказанное является только моим личным мнением, если есть какие-то ошибки, прошу меня поправить! Я буду рад помочь вам понять шаблон публикации-подписки и шаблон наблюдателя!

Если вам интересно посмотреть на мой дерьмовый код, нажмите здесьgithub, Я желаю тебе счастливой жизни!

欢迎关注