Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 28 题:手写发布订阅 #34

Open
lgwebdream opened this issue Jun 19, 2020 · 19 comments
Open

第 28 题:手写发布订阅 #34

lgwebdream opened this issue Jun 19, 2020 · 19 comments
Labels
JavaScript teach_tag 头条 company 滴滴 company

Comments

@lgwebdream
Copy link
Owner

lgwebdream commented Jun 19, 2020

欢迎在下方发表您的优质见解

@lgwebdream lgwebdream added JavaScript teach_tag 头条 company 滴滴 company labels Jun 19, 2020
@Genzhen
Copy link
Collaborator

Genzhen commented Jun 23, 2020

// 发布订阅中心, on-订阅, off取消订阅, emit发布, 内部需要一个单独事件中心caches进行存储;

interface CacheProps {
  [key: string]: Array<((data?: unknown) => void)>;
}

class Observer {

  private caches: CacheProps = {}; // 事件中心

  on (eventName: string, fn: (data?: unknown) => void){ // eventName事件名-独一无二, fn订阅后执行的自定义行为
    this.caches[eventName] = this.caches[eventName] || [];
    this.caches[eventName].push(fn);
  }

  emit (eventName: string, data?: unknown) { // 发布 => 将订阅的事件进行统一执行
    if (this.caches[eventName]) {
      this.caches[eventName].forEach((fn: (data?: unknown) => void) => fn(data));
    }
  }

  off (eventName: string, fn?: (data?: unknown) => void) { // 取消订阅 => 若fn不传, 直接取消该事件所有订阅信息
    if (this.caches[eventName]) {
      const newCaches = fn ? this.caches[eventName].filter(e => e !== fn) : [];
      this.caches[eventName] = newCaches;
    }
  }
  
}

@Genzhen
Copy link
Collaborator

Genzhen commented Jun 23, 2020

class EventListener {
    listeners = {};
    on(name, fn) {
        (this.listeners[name] || (this.listeners[name] = [])).push(fn)
    }
    once(name, fn) {
        let tem = (...args) => {
            this.removeListener(name, fn)
            fn(...args)
        }
        fn.fn = tem
        this.on(name, tem)
    }
    removeListener(name, fn) {
        if (this.listeners[name]) {
            this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
        }
    }
    removeAllListeners(name) {
        if (name && this.listeners[name]) delete this.listeners[name]
        this.listeners = {}
    }
    emit(name, ...args) {
        if (this.listeners[name]) {
            this.listeners[name].forEach(fn => fn.call(this, ...args))
        }
    }
}

@574495412
Copy link

function deepclone(){
type of (obj)!='object ‘ || obj==null
}

@523451928
Copy link

function Events() {
  this.eventHub = {}
}

Events.prototype.$on = function(eventName, fn) {
  this.eventHub[eventName] = (this.eventHub[eventName] || []).concat(fn)
}

Events.prototype.$emit = function(eventName, args) {
  if (this.eventHub[eventName]) {
    this.eventHub[eventName].forEach((fn) => {
      fn(args)
    })
  }
}

Events.prototype.$off = function(eventName, fn) {
  if (this.eventHub[eventName]) {
    if (!fn) {
      this.eventHub[eventName] = []
    } else {
      this.eventHub[eventName] = this.eventHub[eventName].filter(fun => fun !== fn)
    }
  }
}

Events.prototype.$once = function(eventName, fn) {
  const fun = (...args) => {
    fn.apply(this, args)
    this.$off(eventName, fn)
  }
  this.$on(eventName, fun)
}
function test(a) {
  console.log(a)
}
var event = new Events()
event.$once('test', test)
event.$emit('test', 1)

@Genzhen Genzhen closed this as completed Jul 20, 2020
@GolderBrother
Copy link

GolderBrother commented Jul 21, 2020

class EventEmitter {
    constructor(){
        this.events = {};
    }
    on(event, callback){
        const callbacks = this.events[event] || [];
        if(Array.isArray(callbacks)) {
            callbacks.push(callback);
            this.events[event] = callbacks;
        }
        return this;
    }
    off(event, callback){
        const callbacks = (this.events[event] || []).filter(cb => cb !== callback);
        this.events[event] = callbacks;
        return this;
    }
    once(event, callback){
        const wrap = (...args) => {
            typeof callback === 'function' && callback.apply(this, args);;
            this.off(event, wrap);
        }
        this.on(event, wrap);
        return this;
    }
    emit(event) {
        const callbacks = this.events[event] || [];
        if(Array.isArray(callbacks)) {
            callbacks.forEach(cb => typeof cb === 'function' && cb());
        }
        return this;
    }
}

const eventEmitter = new EventEmitter();
eventEmitter.on('click', () => {
    console.log('click 1')
})
eventEmitter.on('click', () => {
    console.log('click 2')
})

// eventEmitter.off('click')
eventEmitter.emit('click')
eventEmitter.once('click')
console.log(eventEmitter);

click 1
click 2
EventEmitter {
  events: { click: [ [Function], [Function], [Function: wrap] ] } }

@Genzhen Genzhen reopened this Jul 29, 2020
@zyronon
Copy link

zyronon commented Oct 14, 2020

class Observer {
        static events = new Map()

        static on(name, fn) {
            this.events.set(name, {isOnce: false, fn})
        }

        static once(name, fn) {
            this.events.set(name, {isOnce: true, fn})
        }

        static off(name) {
            this.events.delete(name)
        }

        static emit(name, data) {
            let cache = this.events.get(name)
            if (cache) {
                if (cache.isOnce) this.events.delete(name)
                cache.fn(data)
            }
        }
    }

@syou-yu
Copy link

syou-yu commented Nov 28, 2020

class EventBus {
  listeners = [];

  on(name, fn) {
    const listener = {
      name,
      fn,
      isOnce: false,
    };
    this.listeners.push(listener);
    return listener;
  }

  once(name, fn) {
    const listener = {
      name,
      fn,
      isOnce: true,
    };
    this.listeners.push(listener);
    return listener;
  }

  off(listener) {
    const index = this.listeners.findIndex(lst => lst === listener);
    if (index > -1) {
      this.listeners.splice(index, 1);
    }
  }

  emit(name, data) {
    for (const listener of this.listeners) {
      if (listener.name === name) {
        try {
          if (listener.isOnce) this.off(listener);
          listener.fn(data);
        } catch (error) {
          console.error('bus emit error', error);
        }
      }
    }
  }
}

const test = new EventBus();

const test1 = test.on('console', () => console.log('123'));
const test2 = test.on('console', () => console.log('456'));
const test3 = test.once('console', () => console.log('789'));

// 第一次
test.emit('console', null);
// 123
// 456
// 789

// 第二次
test.emit('console', null);
// 123
// 456

// 第三次
test.off(test2);
test.emit('console', null);
// 123

@qzruncode
Copy link

熟写发布订阅模式可以锻炼你的增删改查能力
class PubSub {
  sid = 0
  topicList = {}
  constructor(...tops) {
    this.topicList = Object.fromEntries(tops.map(item => [item, []]));
  }
  isValidTopic(topic){
    return Object.keys(this.topicList).includes(topic)
  }
  sub(topic, target) {
    const isValidTopic = this.isValidTopic(topic);
    if(isValidTopic){
      target.id = ++this.sid;
      target[Symbol.for('update')] = function(cb) {
        cb();
      }
      this.topicList[topic].push(target);
    }
  }
  pub(topic, cb) {
    const isValidTopic = this.isValidTopic(topic);
    if(isValidTopic){
      this.topicList[topic].forEach(o => {
        o[Symbol.for('update')](cb);
      })
    }
  }
  unsub(topic, target) {
    const isValidTopic = this.isValidTopic(topic);
    if(isValidTopic){
      const i = this.topicList[topic].findIndex(o => o.id == target.id);
      if(i != -1) {
        this.topicList[topic].splice(i, 1);
      }
    }
  }
}

const pb = new PubSub('china', 'america');
const o1 = {};
const o2 = {};
pb.sub('china', o1);
pb.sub('america', o2);

pb.pub('america', () => {
  console.log('对你实施制裁!!!');
})
pb.pub('china', () => {
  console.log('强烈反对!!!');
})

pb.unsub('china', o1);
pb.unsub('america', o2);

console.log(pb.topicList);

@Luoyuda
Copy link

Luoyuda commented Jun 10, 2021

function EventEmitter(){
    this.caches = {}
}
EventEmitter.prototype.on = function(type, event){
    if(!this.caches[type]) this.caches[type] = []
    this.caches[type].push(event)
    return this
}
EventEmitter.prototype.off = function(type, event){
    if(!this.caches[type]) return
    this.caches[type] = this.caches[type].filter(item => item !== event)
    return this
}
EventEmitter.prototype.once = function(type, event){
    var _event = function(){
        event.apply(this, arguments)
        this.off(type, _event)
    }
    this.on(type, _event)
    return this
}
EventEmitter.prototype.emit = function(){
    var type = arguments[0]
    if(!this.caches[type]) return
    var args = Array.prototype.slice.call(arguments, 1)
    for(let event of this.caches[type]){
        event.apply(this, args)
    }
    return this
}
// 使用如下
var e = new EventEmitter();
e.on('log', console.log)
e.on('log', console.log)
e.emit('log', 1, 2, 3, 4)
e.emit('log', 1, 2, 3, 4)
e.off('log', console.log)
e.emit('log', 1, 2, 3, 4)
e.once('log', console.log)
e.emit('log', 1, 2, 3, 4)
e.emit('log', 1, 2, 3, 4)

@wjiantao
Copy link

//菜鸟一枚,如有出错请多指教
class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [callback];
    } else {
      this.events[eventName].push(callback);
    }
  }
  emit(eventName) {
    this.events[eventName] && this.events[eventName].forEach((cb) => cb());
  }
}

var e = new EventEmitter();

function workDay() {
  console.log("每天工作");
}
function makeMoney() {
  console.log("赚100万");
}
function sayLove() {
  console.log("向喜欢的人示爱");
}

e.on("money", makeMoney);
e.on("work", workDay);
e.on("love", sayLove);

e.emit("money");
e.emit("work");
e.emit("love");

@Charles-ShiZ
Copy link

class EventEmitter {
  constructor(){
    this.events = {}
  }
  on(eventName, callback){
    const callbacks = this.events[eventName]
    if( callbacks ){
      callbacks.add(callback)
    } else {
      this.events[eventName] = new Set([callback])
    }
    return this.events
  }
  once(eventName, callback){
    const onceCallback = (...args)=>{
      callback(...args)
      this.off(eventName, onceCallback)
    }
    this.on(eventName, onceCallback)
    return this.events
  }
  emit(eventName){
    this.events[eventName] && this.events[eventName].forEach(f=>f());
    return this.events
  }
  off(eventName, callback){
    this.events[eventName] && this.events[eventName].delete(callback)
    return this.events
  }
}

@safarishi
Copy link

class Observer {
        static events = new Map()

        static on(name, fn) {
            this.events.set(name, {isOnce: false, fn})
        }

        static once(name, fn) {
            this.events.set(name, {isOnce: true, fn})
        }

        static off(name) {
            this.events.delete(name)
        }

        static emit(name, data) {
            let cache = this.events.get(name)
            if (cache) {
                if (cache.isOnce) this.events.delete(name)
                cache.fn(data)
            }
        }
    }

这里对于监听多次的情况是否未处理

@zizxzy
Copy link

zizxzy commented Nov 3, 2021

订阅和发布的关键在利用一个对象或者map存储事件对象,on是添加事件,off是删除事件,emit是遍历事件对象然后调用

class EventEmitter {
  constructor() {
    this.event = {};
  }

  on(eventName, callbackFunc) {
    const callback = this.event[eventName] || [];
    if (Array.isArray(callback)) {
      callback.push(callbackFunc);
      this.event[eventName] = callback;
    }
    return this.event;
  }

  off(eventName, callback) {
    if (this.event[eventName]) {
      const newEventCallback = this.event[eventName].filter(c => c !== callback);
      this.event[eventName] = newEventCallback;
    }
    return this.event;
  }

  emit(eventName, data) {
    if (this.event[eventName]) {
      this.event[eventName].forEach(callback => {
        callback && callback(data);
      });
    }
    return this.event;
  }
}

const eventEmitter = new EventEmitter();
const clickFunc = function () {
  console.log('click 1');
}
eventEmitter.on('click', clickFunc)
eventEmitter.on('click', () => {
  console.log('click 2')
})
eventEmitter.emit('click')
eventEmitter.off('click', clickFunc);
eventEmitter.emit('click')

@SnailOwO
Copy link

SnailOwO commented Nov 11, 2021

class eventEmitter {
        list = {}

        on(event, fn) {
            (this.list[event] || (this.list[event] = [])).push(fn)
            return this
        }

        off(event, fn) {
            const curFns = this.list[event] || []
            if (!curFns.length) {
                return false
            }
            for (const key of Object.keys(curFns)) {
                const val = curFns[key]
                if (val === fn || val.fn === fn) {
                    curFns.splice(key, 1)
                    break
                }
            }
            return this
        }

        once(event, fn) {
            const on = () => {
                fn.apply(this, arguments)
                this.off(event, on)
            }
            this.on(event, on)
            return this
        }

        emit() {
            const curEvent = Array.prototype.shift.apply(arguments)
            const curFns = this.list[curEvent]
            if (!curFns.length) {
                return false
            }
            for (const val of curFns) {
                val.apply(this, arguments)
            }
        }
    }
   const eventEmit = new eventEmitter()
   eventEmit.on('article1', user3).emit('article1', 'test111');
   function user3(content) {
        console.log('用户3订阅了:', content);
    }

@18602435705
Copy link

class EventListener {
  constructor() {
    this.events = {};
  }
  on(e, cb) {
    const { events } = this;
    (events[e] || (events[e] = [])).push(cb);
  }

  once(e, cb) {
    const once = (...args) => {
      cb(...args);
      this.off(e, once);
    };
    once.fn = cb;
    this.on(e, once);
  }

  off(e, cb) {
    const cbs = this.events[e] || [];
    let item;
    let i = cbs.length;
    while (i--) {
      item = cbs[i];
      // 兼容移除once事件
      if (cb === item || cb === item.fn) {
        cbs.splice(i, 1);
        break;
      }
    }
  }

  emit(e, ...args) {
    const { events } = this;
    (events[e] || []).forEach((cb) => cb(...args));
  }
}

// 测试
const eventer = new EventListener();
const f1 = () => console.log(111);
const f2 = () => console.log(222);
eventer.on("click", f1);
eventer.once("click", f2);
eventer.off("click", f2);
eventer.emit("click");

@syou-yu
Copy link

syou-yu commented Dec 25, 2021 via email

@wringY
Copy link

wringY commented Apr 3, 2022

class Observer {
    constructor() {
        this.eventsMap = {}
    }

    on(type, callback) {
        if (!this.eventsMap[type]) {
            this.eventsMap[type] = []
        }
        this.eventsMap[type].push({once: false, callback})
    }
    once(type, callback) {
        if (!this.eventsMap[type]) {
            this.eventsMap[type] = []
        }
        this.eventsMap[type].push({once: true, callback})
    }
    emit(type, payload) {
        let queue = this.eventsMap[type]
        let index = 0;
        while (index < queue.length && queue) {
            const depObj = [...queue].shift()
            if (depObj.once) {
                depObj.callback(payload)
                queue.splice(index, 1)
            } else {
                depObj.callback(payload)
            }
            index++
        }
    }
}

const ev = new Observer()
ev.on('change', function(res) {
    console.log(res)
})

// ev.emit('change', 10)
// ev.emit('change', 10)
// ev.emit('change', 10)

ev.once('input', function(res) {
    console.log(res)
})
ev.emit('input', 20)
ev.emit('input', 20)
ev.emit('input', 20)

@Kisthanny
Copy link

/**
 * 手写发布订阅
 * 比如vue的自定义事件
 * 父组件 v-on:('update',(e)=>{console.log(e)})
 * 子组件 $emit('update', {data:1})
 *
 * 发布订阅是一个类
 * 一般有4个方法
 * $on: 入参为一个事件名,一个回调函数
 * $emit: 入参为一个事件名,一个传给回调函数的数据
 * $once: 与$on类似,但是触发一次回调后就被销毁
 * $off: 入参为一个事件名,一个对应的$on注册时的回调function
 */

class EventEmitter {
  constructor() {
    this.eventHub = {};
  }
  $on(name, fn) {
    if (!this.eventHub[name]) {
      this.eventHub[name] = [];
    }
    this.eventHub[name].push(fn);
  }
  $emit(name, ...data) {
    if (this.eventHub[name]) {
      this.eventHub[name].forEach((fn) => {
        fn(...data);
      });
    }
  }
  $off(name, fn) {
    if (!this.eventHub[name]) {
      return;
    }
    if (fn === undefined) {
      this.eventHub[name] = [];
    } else {
      this.eventHub[name] = this.eventHub[name].filter((cb) => cb !== fn);
    }
  }
  $once(name, fn) {
    const wrap = (...args) => {
      this.$off(name, wrap);
      fn(...args);
    };
    this.$on(name, wrap);
  }
}

@syou-yu
Copy link

syou-yu commented Mar 21, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript teach_tag 头条 company 滴滴 company
Projects
None yet
Development

No branches or pull requests