blog icon indicating copy to clipboard operation
blog copied to clipboard

events实现原理

Open wuxianqiang opened this issue 4 years ago • 0 comments

function checkListener(listener) {
  if (typeof listener !== 'function') {
    throw new Error('listener', 'Function', listener);
  }
}

function spliceOne(list, index) {
  for (; index + 1 < list.length; index++)
    list[index] = list[index + 1];
  list.pop();
}

function EventEmitter() {
  EventEmitter.init.call(this)
}

EventEmitter.prototype._events = undefined
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined

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

function _addListener(target, type, listener) {
  let events;
  let exiting;
  checkListener(listener)
  events = target._events
  if (events === undefined) {
    events = target._events = Object.create(null)
    target._eventsCount = 0;
  } else {
    exiting = events[type]
  }
  if (exiting === undefined) {
    // { add: fn }
    events[type] = listener;
    ++target._eventsCount;
  } else {
    // { add: [fn, fn] }
    if (typeof exiting === 'function') {
      exiting = events[type] = [exiting, listener]
    } else {
      exiting.push(listener)
    }
  }
  console.log(events)
  return target
}

EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener)
}

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.removeListener = function removeListener (type, listener) {
  let list;
  let events;
  checkListener(listener)
  events = this._events;
  if (events === undefined) {
    return this;
  }
  list = events[type]
  if (list === undefined) {
    return this;
  }
  if (list === listener || list.listener === listener) {
    if (--this._eventsCount === 0) {
      // 如果只有一个直接清空
      this._events = Object.create(null)
    } else {
      // 删除当前的那个
      delete events[type];
    }
  }
}

EventEmitter.prototype.of = EventEmitter.prototype.removeListener

EventEmitter.prototype.emit = function emit(type, ...args) {
  const events = this._events;
  const handler = events[type]
  if (handler === undefined) {
    return false;
  }
  if (typeof handler === 'function') {
    Reflect.apply(handler, this, args)
  } else {
    const len = handler.length;
    const listeners = arrayClone(handler, len);
    for (let i = 0; i < len; i++) {
      Reflect.apply(listeners[i], this, args)
    }
  }
  return true
}

function arrayClone(arr, n) {
  var copy = new Array(n);
  for (var i = 0; i < n; ++i)
    copy[i] = arr[i];
  return copy;
}


function _onceWrap(target, type, listener) {
  let state = { fired: false, wrapFn: undefined, target, type, listener }
  var wrapped = onceWrapper.bind(state);
  // 给绑定之后的函数增加属性listener
  wrapped.listener = listener;
  state.wrapFn = wrapped;
  return wrapped
}

function onceWrapper(...args) {
  // once只是用函数包了一次里面通过一个标识符控制的
  if (!this.fired) {
    this.target.removeListener(this.type, this.wrapFn);
    this.fired = true;
    Reflect.apply(this.listener, this.target, args)
  }
}

EventEmitter.prototype.once = function once(type, listener) {
  checkListener(listener)
  this.on(type, _onceWrap(this, type, listener))
  return this
}


class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.once('event', () => {
  console.log('once');
});
myEmitter.emit('event');
// 打印: once
myEmitter.emit('event');
// 不触发

wuxianqiang avatar May 02 '20 07:05 wuxianqiang