Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

观察者模式 #20

Open
bruce-16 opened this issue Aug 24, 2019 · 0 comments
Open

观察者模式 #20

bruce-16 opened this issue Aug 24, 2019 · 0 comments

Comments

@bruce-16
Copy link
Owner

bruce-16 commented Aug 24, 2019

js的世界中,事件处理随处可见,这种处理模式就是观察者模式。这种模式会让对象和对象之间建立一种依赖关系,当一个对象改变后,将会通知其他对象进行响应。

js中可以应用在异步处理中,在事件回调函数中触发事件,在外部监听处理事件,来避免回调函数的嵌套。

nodejs中的events模块就是一个观察者模式的实现。

对于一个基本的eventEmitter对象,应该有添加事件监听的方法、移除事件监听的方法和触发某一个事件的方法,在此基础上,这里再添加一个方法用来实现监听只会被触发一次的方法。

  1. addEventListener(eventName, listener [, prepend])
  2. removeEventListener(eventName, listener)
  3. once(eventName, listener)
  4. emit(eventName, ...args)

下面代码使用es6实现,简洁一点。

使用 class 声明一个 EventEmitter

class EventEmitter {
  constructor() {
    this.events = {}
  }
}

构造函数里面只有一个属性的初始化,events 属性就来存放事件和处理方法的映射关系。

addEventListener

class EventEmitter {
  ...
  addEventListener(event, listener, prepend) {
    this.events[event] = this.events[event] || []
    if (prepend) {
      this.events[event].unshift(listener)
    } else {
      this.events[event].push(listener)
    }
  }
}
  1. 第三个参数表示将处理回调函数放在对应事件队列的最前面,默认放在最后面。主要就是调用顺序的问题。
  2. 添加事件监听的方法的核心就是将对应的处理函数放入到队列中。

removeEventListener

class EventEmitter {
...
  removeEventListener (event, listener) {
    if (!this.events[event]){
      return
    }
    if (!listener) {
      this.events[event] = undefined
    } else {
      this.events[event] = this.events[event].filter(e => e !== listener && e.origin !== listener)
    }
  }
}
  1. 移除对应的事件处理函数的方法就是将传入的listener在队列中过滤掉。
  2. 可以看到过滤的方式有一个e.origin !== listener比较方式,这里主要是为了兼容once函数添加的监听,往下看就明白了。

once

使用once函数添加的事件监听处理函数,只会被触发一次,也就是当调用一次之后就自动移除掉。

class EventEmitter {
...
  once (event, listener) {
    const cb = (...args) => {
      listener(...args)
      this.removeEventListener(event, listener)
    }
    cb.origin = listener
    this.addEventListener(event, cb)
  }
}
  1. 首先重新声明了一个函数,函数内部来调用真实的回调函数,调用完之后再次调用对象的removeEventListener方法来进行手动移除处理方法。注意这里,如果没有使用箭头函数,是需要将当前的this绑定到cb函数中。
  2. cb函数的origin属性指向listener,看到这里,就可以知道为什么removeEventListener要多一个条件判断,因为这里使用addEventListener添加的listener并不是原始的处理函数,而是封住一次的另外一个函数,通过origin这个属性,我们才能在removeEventListener函数中识别对应的处理函数。

emit

class EventEmitter {
...
  emit (event, ...args) {
    if (!this.events[event]) {
      return
    }
    this.events[event].forEach(listener => listener.call(this, ...args))
  }
}

触发事件就是将对象事件的处理队列遍历的调用一遍,这里注意this的指向和参数的传递。

小结

观察者模式相对比较简单,当然还有很多其他扩展方法,这里就先不说了,了解主要思想最重要。重点在于怎么去维护好对应的事件队列。

参考文章: awesome-coding-js

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant