DOM 当中有一系列事件, 比如 click, mousedown 等. 添加这些事件回调的操作, 前端程序员当然都是轻车熟路了.
假如要添加自定义事件呢?
添加事件
背景
比如一群小伙伴, 就大雄, 静香和小夫吧, 结伴出去玩, 约定在公园会和, 先到公园的小伙伴, 需要告诉其他人, 我已经到了. 而收到消息的小伙伴, 在路上的, 可能要加快步伐, 还在挑衣服的, 可能也要快点出门了.
这里有两个约定的事件, 就是 "到达" 和 "收到信息", 到达的小伙伴, 需要发消息通知其他人; 收到信息的小伙伴, 要加快进度了.
用 JavaScript 的方式描述一下, 每个小伙伴都需要监听 "到达" 和 "收到信息" 这两个事件, 然后做出相应的反应, 俗称回调函数.
核心代码
而 JavaScript 内部是没有 "到达" 和 "收到信息" 的事件的, 这个时候, 自定义事件就派上用场了, 先上代码:
- function EventCenter() {
- this.handlers = {}
- }
- EventCenter.prototype = {
- constructor: EventCenter,
- listen: function(type, handler) {
- if (this.handlers[type] === undefined) {
- this.handlers[type] = []
- }
- this.handlers[type].push(handler)
- },
- trigger: function(type, ...args) {
- const typeHandlers = this.handlers[type]
- if (typeHandlers === undefined || typeHandlers.length === 0) {
- return
- }
- for (let i = 0; i <typeHandlers.length; i++) {
- typeHandlers[i].apply(this, args)
- }
- }
- }
代码中, 首先定义了构造函数 EventCenter, 这个构造函数的实例, 将帮助我们添加和处理自定义事件.
构造函数内部, 首先声明了空对象 handlers , 用来存放事件处理函数.
在 EventCenter 的 prototype 上, 先将 constructor 改为 EventCenter. 这样一来, new EventCenter() instanceof EventCenter 就会返回 true, 说明 new 出来的结果确实是 EventCenter 实例.
listen 方法接受两个参数 type 和 handler, type 是字符串, 代表事件的类型, 类似我们最熟悉的 click; 而 handler 是函数, 代表事件处理函数. listen 将 handler 添加到添加到 type 事件的处理函数集合中.
listener 内部首先判断 type 类型的处理函数集合 this.handlers[type] 是否存在, 如果没有, 就将其初始化为一个空数组. 然后将 handler 添加到这个数组里面.
trigger 方法至少接受一个参数 type, 它将触发 type 类型的所有事件处理函数. trigger 多余的参数, 将传递给事件处理函数.
trigger 内部首先取得 type 事件的处理函数集合, 并将其赋值给 typeHandlers. 试图触发一个没有被 listen 过的事件时, typeHandlers 是 undefined, 所以要首先排除这种情况. 接下来, 调用 for 循环, 依次执行该类型的事件处理函数, 调用 apply 方法将 this 指向 trigger 的 this, 将 trigger 函数的多余参数传递给事件处理函数.
应用一下
在这里, 我们会将每个小伙伴都初始化为一个 EventCenter 实例, 将 "到达" 事件定义为 arrive , 将 "收到消息" 事件定义为 message. 小伙伴将处理 arrive 和 message 事件.
- function sendMessage(message, list) {
- if (!Array.isArray(list)) {
- return
- }
- for (let i = 0; i < list.length; i++) {
- list[i].trigger('message', message)
- }
- }
- let daxiong = new EventCenter()
- let xiaofu = new EventCenter()
- let jingxiang = new EventCenter()
- daxiong.listen('arrive', function() {
- console.log('大雄到了, 给其他小伙伴发消息.')
- sendMessage('大雄: 我到了.', [jingxiang, xiaofu])
- })
- jingxiang.listen('message', function(message) {
- console.log('静香收到消息:' + message)
- console.log('静香: 我要赶紧出门了.')
- })
- xiaofu.listen('message', function(message) {
- console.log('小夫收到消息:' + message)
- console.log('小夫: 切.')
- })
- daxiong.trigger('arrive')
运行结果如下:
函数 sendMessage, 用来发送消息给小伙伴, 接受两个参数 message 和 list. message 是消息内容, list 代表一个名单, 名单上的小伙伴将收到消息.
sendMessage 内部, 首先判断 list 是不是数组, 若不是, 就终止函数执行. 否则, 调用 for 循环, 依次触发 list 名单上每个小伙伴的 message 事件, 并把 message 传递过去.
然后初始化三个 EventCenter, 大雄, 小夫和静香.
接下来, 大雄调用 listen 方法, 添加 arrive 的处理函数. 大雄一到公园, 将消息发送给静香和小夫.
调用静香的 listen 方法, 为静香添加 message 的处理函数.
接着调用小夫的 listen 方法, 为小夫添加 message 处理函数.
大雄到了, 利用 daxiong.trigger('arrive') 触发大雄的到达事件.
移除事件
除了添加事件监听, 常见的场景还有移除事件的监听.
在这里我们假设, 小夫放了他俩鸽子, 这个时候就要取消小夫的 message 事件.
核心代码
首先来完善 EventCenter 的代码:
- EventCenter.prototype = {
- ... // 省略 EventCenter 已有的代码.
- remove: function(type, handler) {
- if (this.handlers[type] === undefined) {
- return
- }
- this.handlers[type] = this.handlers[type].filter((item) => item !== handler)
- }
- }
remove 方法接收两个参数, 一个是表示时间类型的 type, 另一个 handler 则代表要删除的事件处理函数.
方法内部首先取得 type 类型事件的处理函数集合, 同样, 这里也要判断这个集合是否存在. 关键的删除语句, 用的是 filter 方法, 返回事件处理集合中除了 handler 的所有函数.
应用一下
来用一下, 借助多啦 A 梦的时光机, 回到一开始. 这个时候, 大雄还没到公园.
- daxiong.listen('arrive', function() {
- console.log('大雄到了, 给其他小伙伴发消息.')
- sendMessage('大雄: 我到了.', [jingxiang, xiaofu])
- })
- jingxiang.listen('message', function(message) {
- console.log('静香收到消息:' + message)
- console.log('静香: 我要赶紧出门了.')
- })
- const messageHandlerXf = function(message) {
- console.log('小夫收到消息:' + message)
- console.log('小夫: 切.')
- })
- xiaofu.listen('message', messageHandlerXf)
- // 小夫遇到胖虎, 被强行拉去打棒球.
- // 注意, 他要开始鸽了.
- xiaofu.remove('message', messageHandlerXf)
- daxiong.trigger('arrive')
运行结果如下图:
在这里, 大雄和静香的代码与之前的别无二致. 小夫 listen 前, 将匿名函数赋值给变量 messageHandlerXf, 因为 remove 是根据函数名来移除事件处理函数的.
之后用 listen 添加 messageHandlerXf 作为小夫的 message 事件处理函数.
看到注释, 我们知道, 小夫开始放鸽子, 不关心消息了. 好, 这里就调用 remove 来移除 message 事件的处理函数 messageHandlerXf.
大雄到了, 触发 arrive 事件, 给他俩发消息.
看运行结果可知, remove 成功. 大雄塞翁失马, 焉知非福.
总结
大雄的故事告诉我们, 如何用 JavaScript 添加自定义事件.
这里实际上用到了经典的观察者模式, 也叫做发布 - 订阅模式. 顾名思义, 观察者模式, 当被观察者产生变化, 观察者可以知道; 发布订阅模式, 发布者发布消息, 订阅者可收到通知.
这个时候, 发布代码与订阅代码的耦合性是很低的, 十分利于代码的维护.
来源: http://www.jianshu.com/p/d2ec70ce1e1d