原生js实现事件监听类on/emit/off方法,Vue event事件机制解读
Author:zhoulujun Date:
原生js封装事件绑定$on、事件触发$emit和事件移除$off,如何实现?
事件分析:一对多,观察者模式
建立事件仓库:obj{key:val,key:val}
事件绑定on(name,fn):先判断事件名称name在事件仓库中是否存在,不存在,则初始化obj[name]=[];将需要绑定的事件push进数组中.
事件触发emit(name,val):先判断事件名称name在事件仓库中是否存在,存在则遍历数组每个fn元素,调用事件;val为传递的参数.
事件移除of(name,fn):先判断事件名称name在事件仓库中是否存在,再判断fn是否有,最后判断fn是否存在数组中,存在则删除fn数组元素;若无fn则清空数组.
es6 代码如下
class Event { constructor () { this.obj = {} } /** * 监听事件 * 事件为一个对象,对应的时间名字为一个数组,当发送次事件是,一次执行数组存储的行数 * @param name {string} 事件名称 * @param fn {function} 监听到事件后,执行函数 */ on (name, fn) { if (!this.obj[name]) { this.obj[name] = [] } this.obj[name].push(fn) // 链式调用 return this } // 移除事件 off (name, fn) { if (!this.obj[name]) { return false } if (!fn) { this.obj[name] = [] } if (!Array.isArray(this.obj[name]) || !this.obj[name].length) { return false } let index = this.obj[name].indexOf(fn) if (index === -1) { obj[name].length = 0; // 设长度为0比obj[name] = []更优,因为如果是空数组则又开辟了一个新空间,设长度为0则不必开辟新空间 // this.obj[name] = [] return false } this.obj[name].splice(index, 1) return this } // 发送事件 emit (name, val) { if (!this.obj[name]) { return false } this.obj[name].map(fn => { fn(val) }) return this } } let event = new Event() event.on('test', (val) => { console.log(val) }) event.emit('test', 2) event.off('test') event.emit('test', 3)
github 地址参考 https://github.com/zhoulujun/GoBang_Renju_es6_project/blob/master/src/games/EventListen.js
Vue 实现事件 $on $off $emit 实现方式
源码如下:node_modules/vue/src/core/instance/events.js
/* @flow */ import { tip, toArray, hyphenate, handleError, formatComponentName } from '../util/index' import { updateListeners } from '../vdom/helpers/index' export function initEvents (vm: Component) { 在vue底下挂载一个 event 事件对象 vm._events = Object.create(null) vm._hasHookEvent = false // init parent attached events const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } } let target: any function add (event, fn) { target.$on(event, fn) } function remove (event, fn) { target.$off(event, fn) } function createOnceHandler (event, fn) { const _target = target return function onceHandler () { const res = fn.apply(null, arguments) if (res !== null) { _target.$off(event, onceHandler) } } } export function updateComponentListeners ( vm: Component, listeners: Object, oldListeners: ?Object ) { target = vm updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm) target = undefined } export function eventsMixin (Vue: Class<Component>) { const hookRE = /^hook:/ Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { const vm: Component = this // 如果对象是数组,遍历数据,执行$on 方法 if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn) } } else { (vm._events[event] || (vm._events[event] = [])).push(fn) // optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookup if (hookRE.test(event)) { vm._hasHookEvent = true } } return vm } Vue.prototype.$once = function (event: string, fn: Function): Component { const vm: Component = this function on () { vm.$off(event, on) fn.apply(vm, arguments) } on.fn = fn vm.$on(event, on) return vm } Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { const vm: Component = this // all if (!arguments.length) { vm._events = Object.create(null) return vm } // array of events if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn) } return vm } // specific event const cbs = vm._events[event] if (!cbs) { return vm } if (!fn) { vm._events[event] = null return vm } if (fn) { // specific handler let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { cbs.splice(i, 1) break } } } return vm } Vue.prototype.$emit = function (event: string): Component { const vm: Component = this if (process.env.NODE_ENV !== 'production') { const lowerCaseEvent = event.toLowerCase() if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { tip( `Event "${lowerCaseEvent}" is emitted in component ` + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + `Note that HTML attributes are case-insensitive and you cannot use ` + `v-on to listen to camelCase events when using in-DOM templates. ` + `You should probably use "${hyphenate(event)}" instead of "${event}".` ) } } let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) for (let i = 0, l = cbs.length; i < l; i++) { try { cbs[i].apply(vm, args) } catch (e) { handleError(e, vm, `event handler for "${event}"`) } } } return vm } }
代码解读方面,有人做了:Vue.js源码解读系列 - Vue的自定义事件机制 https://blog.seosiwei.com/detail/23
其是这个就是个发布订阅模型
发布订阅模型
如果安装发布订阅模式来写,参考:
观察者模式与发布订阅模式的区别 https://www.zhoulujun.cn/html/theory/engineering/model/9072.html
转载本站文章《原生js实现事件监听类on/emit/off方法,Vue event事件机制解读》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js/2016_0624_8438.html