前言
该文录实例方法集
正文
eventsMixin
收录事件相关实例方法,我们知道其实它就是发布订阅相关方法
vm.$on
订阅方法
// 用于检测是否是hook事件
const hookRE = /^hook:/
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++) {
this.$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
}
首先定义变量hookRE
用于判定当前实例是否有注册hook
事件
我们知道这个函数event
参数支持字符串和数组,所以先判断订阅的事件名是不是数组,是的话就遍历递归即可,否的话就简单入队到vm._events
这个事件中心
这里做个优化其实就是设置一个实例属性
_hasHookEvent
,只要判断到这个实例对象有注册了hook
事件就置为true
,这样子就可以在callHook(vm, 'beforeCreate')
之类时不用callHook(vm, 'hook:beforeCreate')
,只要在callHook
判断下_hasHookEvent
然后加个前缀(hook:
)即可
vm.$once
一次订阅方法
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
}
这个其实就是在其回调触发时取消该事件订阅即可,所以我们把传入的回调fn
做下劫持就是了on.fn = fn
这句使得我们在$off、$on
可以通过参数on
取到原本传入的fn
回调
vm.$off
取消订阅方法
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
// this.$off()
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
// this.$off(['test1', 'test2'], fn)
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$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]
// cb.fn其实是给$once使用的
// 这里就是判断下要退订的fn和已注册的是否一致
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
return vm
}
这里更多的是$off
的各种传参兼容,很简单不赘述
- 如果没有提供参数,则移除所有的事件监听器
- 如果只提供了事件,则移除该事件所有的监听器
- 如果同时提供了事件与回调,则只移除这个回调的监听器
vm.$emit
触发订阅更新
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
// https://cn.vuejs.org/v2/guide/components-custom-events.html#%E4%BA%8B%E4%BB%B6%E5%90%8D
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
}
首先就是在非开发环境下判断下事件名的问题,其实是事件名kebab-case vs camelCase 或 PascalCase
。也就是HTML
不区分大小写,所以建议使用kebab-case
而不是驼峰命名法
然后就是取到事件回调以及取参数,毕竟$emit
是可以传参数的
值得注意的是这里的cbs
肯定是数组,这是在$on
里_events
赋值的逻辑
最后就是遍历回调,执行回调就是了
这里判断下
cbs
是不是数组长度大于1
是的话toArray
转下,其实感觉没这必要,cbs
必定是数组