Fe-interview: 第 28 题:手写发布订阅

Created on 19 Jun 2020  ·  8Comments  ·  Source: lgwebdream/FE-Interview

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

JavaScript 头条 滴滴

Most helpful comment

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

All 8 comments

// 发布订阅中心, 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;
    }
  }

}
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))
        }
    }
}

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

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)
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] ] } }
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)
            }
        }
    }
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
熟写发布订阅模式可以锻炼你的增删改查能力
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);
Was this page helpful?
0 / 5 - 0 ratings