这里也很简单啦,获取该事件对应的回调数组。如果不为空,就去寻找需要移除的回调,找到后,直接删除,然后将新的回调数组覆盖原来的那个就可以了
emit
if (listeners && listeners.length) { listeners.forEach((listener) => { listener.callback.call(listener.vm,...args) }); return true; } return false;
这里就是监听到事件后,执行该事件对应的回调函数,注意这里的call,因为监听到事件后我们可能要修改下vue实例的数据或者调用一些方法,用过vue的同学都知道我们都是this.xxx来调用的,所以一定得将回调函数的this指向vue实例,这也是为什么存回调事件时也要把vue实例存下来的原因。
Observer.js
constructor(connection, store) { // 这里很明白吧,就是判断这个connection是什么类型 // 这里的处理就是你可以传入一个连接好的socket实例,也可以是一个url if(typeof connection == 'string'){ this.Socket = Socket(connection); }else{ this.Socket = connection } // 如果有传进vuex的store可以响应在store中写的mutations和actions // 这里只是挂载在这个oberver实例上 if(store) this.store = store; // 监听,启动! this.onEvent() }
这个Observer.js里也主要是写了一个Observer的class,以上是它的构造函数,构造函数第一件事是判断connection是不是字符串,如果是就构建一个socket实例,如果不是,就大概是个socket的实例了,然后直接挂载在它的对象实例上。其实这里我觉得可以参数检查严格点, 比如字符串被人搞怪地可能会传入一个非法的url,对吧。这个时候判断下,抛出一个error提醒下也好,不过应该也没人这么无聊吧,2333。然后如果传入了store,也挂在对象实例上吧。最后就启动监听事件啦。我们看看onEvent的逻辑
onEvent(){ // 监听服务端发来的事件,packet.data是一个数组 // 第一项是事件,第二个是服务端传来的数据 // 然后用emit通知订阅了该信号的回调函数执行 // 如果有传入了vuex的store,将该事件和数据传入passToStore,执行passToStore的逻辑 var super_onevent = this.Socket.onevent; this.Socket.onevent = (packet) => { super_onevent.call(this.Socket, packet); Emitter.emit(packet.data[0], packet.data[1]); if(this.store) this.passToStore('SOCKET_'+packet.data[0], [ ...packet.data.slice(1)]) }; // 这里跟上面意思应该是一样的,我很好奇为什么要分开写,难道上面的写法不会监听到下面的信号? // 然后这里用一个变量暂存this // 但是下面都是箭头函数了,我觉得没必要,毕竟箭头函数会自动绑定父级上下文的this let _this = this; ["connect", "error", "disconnect", "reconnect", "reconnect_attempt", "reconnecting", "reconnect_error", "reconnect_failed", "connect_error", "connect_timeout", "connecting", "ping", "pong"] .forEach((value) => { _this.Socket.on(value, (data) => { Emitter.emit(value, data); if(_this.store) _this.passToStore('SOCKET_'+value, data) }) }) }
这里就是有点类似重载onevent这个函数了,监听到事件后,将数据拆包,然后通知执行回调和传递给store。大体的逻辑是这样子。然后这代码实现有两部分,第一部分和第二部分逻辑基本一样。只是分开写。(其实我也不是很懂啦,如果很有必要的话,我猜第一部分的写法还监听不了第二部分的事件吧,所以要另外监听)。最后只剩下一个passToStore了,其实也很容易懂
passToStore(event, payload){ // 如果事件不是以SOCKET_开头的就不用管了 if(!event.startsWith('SOCKET_')) return // 这里遍历vuex的store中的mutations for(let namespaced in this.store._mutations) { // 下面的操作是因为,如果store中有module是开了namespaced的,会在mutation的名字前加上 xxx/ // 这里将mutation的名字拿出来 let mutation = namespaced.split('https://www.jb51.net/').pop() // 如果名字和事件是全等的,那就发起一个commit去执行这个mutation // 也因此,mutation的名字一定得是 SOCKET_开头的了 if(mutation === event.toUpperCase()) this.store.commit(namespaced, payload) } // 这里类似上面 for(let namespaced in this.store._actions) { let action = namespaced.split('https://www.jb51.net/').pop() // 这里强制要求了action的名字要以 socket_ 开头 if(!action.startsWith('socket_')) continue // 这里就是将事件转成驼峰式 let camelcased = 'socket_'+event .replace('SOCKET_', '') .replace(/^([A-Z])|[\W\s_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()) // 如果action和事件全等,那就发起这个action if(action === camelcased) this.store.dispatch(namespaced, payload) } }