使用方式
-
$emit
和$on
的使用方法-
vm.$on('test', function (msg) { console.log(msg) }) vm.$emit('test', 'hi') // => "hi"
-
-
$off
和once
的使用方法-
this.$off('event_name')
- 移除自定义事件监听器。
- 如果没有提供参数,则移除所有的事件监听器;
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器 ,删除事件名称对应事件数组中执行的函数方法。
- 移除自定义事件监听器。
-
this.$once('event_name',function(){})
- 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
-
实现方式
-
$on
-
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { const vm: Component = this 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) if (hookRE.test(event)) { vm._hasHookEvent = true } } return vm }
- 方法传入的
event
参数可以是数组或是字符串 - 如果是字符串就将该方法放置到
_events[event]
数组中,判断一下是否是hook:
事件,如果是需要在对象上添加属性 - 如果是数组的话就将数组里面的数据循环存放到
this._event[event]
中
- 方法传入的
-
-
$emit
-
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) // 只获取索引从1开始之后的数据 const info = `event handler for "${event}"` for (let i = 0, l = cbs.length; i < l; i++) { invokeWithErrorHandling(cbs[i], vm, args, vm, info) } } return vm }
- 开发环境下:
event
事件名称不是全是小写,并且this._events[event]
中之前已经注册过了全是小写的方法就将触发提示 - 循环执行
this._events[event]
中的方法
- 开发环境下:
-
// 讲一个数组对象转换成真正的数组 export function toArray (list: any, start?: number): Array<any> { start = start || 0 let i = list.length - start const ret: Array<any> = new Array(i) while (i--) { ret[i] = list[i + start] } return ret }
- 可以获取指定位置开始的数组数据
-
export function invokeWithErrorHandling ( handler: Function, context: any, args: null | any[], vm: any, info: string ) { let res try { res = args ? handler.apply(context, args) : handler.call(context) if (res && !res._isVue && isPromise(res) && !res._handled) { res.catch(e => handleError(e, vm, info + ` (Promise/async)`)) res._handled = true } } catch (e) { handleError(e, vm, info) } return res }
- 执行事件函数,并提示相关的错误信息
-
this.$emit
和this.$on
是如何实现在子组件中触发了$emit
之后$on
监听到数据了呢?
回答: 当编译之后this.$on
所绑定的事件和方法都被放置到this._events
数组中,当在子组件中触发了this.$emit
之后将获取this._events
中的对应的事件进行执行。
-
$off
-
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { const vm: Component = this if (!arguments.length) { // 如果没有传入参数就将直接返回不执行 vm._events = Object.create(null) return vm } if (Array.isArray(event)) {// 如果传入的事件参数是个数组就循环将数组中的事件进行解绑 for (let i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn) } return vm } const cbs = vm._events[event] if (!cbs) { // 如果传入的事件名称没有找到对应的执行函数就将直接返回 return vm } if (!fn) { // 如果没有传入回调函数就将当前的事件名称对应的事件函数进行解绑置空 vm._events[event] = null return vm } let cb let i = cbs.length while (i--) { cb = cbs[i] if (cb === fn || cb.fn === fn) { // 删除事件名称对应的事件数组中指定的函数方法 cbs.splice(i, 1) break } } return vm }
- 主要是就是根据
event
事件名称来在this._events
中删除指定的事件方法
- 主要是就是根据
-
-
$once
-
Vue.prototype.$once = function (event: string, fn: Function): Component { const vm: Component = this function on () { // once主要是删除在this._events中的事件方法 然后执行事件 vm.$off(event, on) fn.apply(vm, arguments) } on.fn = fn vm.$on(event, on) // 先在this._events中绑定事件 return vm }
- 先利用
this.$on
方法在this._events[event]
中绑定事件,然后将在this.$emit
执行的时候,调用on
方法。在on
回调中利用this.off
来删除this._events
中指定的方法,然后再对方法进行执行从而实现只执行了一次。
- 先利用
-