《一天读一个Vue源文件》--events.js
前情回顾
上篇文章大致分析了Vue 的init
方法,大致的流程是,构造函数接收options
,然后构造函数中调用this._init(options)
,之后通过initMixin(Vue)
方法,在构造函数的原型上添加_init(options)
方法。_init
方法的主要功能是合并options
,同时设置proxy
。代码框架如下:
// index.js function Vue(options){ this._init(options) // init相关 initMixin(vue) // 事件相关 eventsMixin(Vue) } // init.js export function initMixin(Vue){ Vue.prototype._init = function(options){ const vm = this; // 合并options if(options){ mergeOptions() } // 设置代理 initProxy(vm) // 生命周期 initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ... // 最后调用$mount() if(vm.$options.el){ vm.$mount(vm.$options.el) } } }
initLifecycle(vm)
initLifecycle(vm)
定义在src/core/instance/lifecycle.js
中。该方法似乎什么也没做,只是在Vue的私有属性上添加了默认值。该方法代码如下:
export function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent.$children.push(vm) } vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }
可以看出,该方法只是设置了
vm
的$parent,$root,$children,$refs,_watcher,__isMounted,_isDestroyed,_isbeingDestroyed
的默认值。
重点内容
执行完initLifecycle(vm)
添加了属性的默认值之后,接着执行了initEvent(vm)
方法,然而我们先不考虑initEvent(vm)
,重点关注一下,文章开头的eventsMixin(Vue)
。
之前也知道vue的事件系统是基于发布订阅模式
,也知道发布订阅模式的简单实现如下:
let event = { listener:[], add:(name,fn)=>{ if( !this.listener[ name ] ){ this.listener[name] = [] } this.listener[name].push(fn) }, trigger:function (key)=>{ let fns = this.listener[key] if(!fns || fns.length ==0){ return } for(let i = 0;i<fns.length;i++){ fns[i].call(this,arguments) } } }
大致流程就是用数组缓存,事件名及对应的函数,然后触发事件时,遍历缓存列表,执行对应的函数。
eventsMixin(Vue)
这个方法就是将上面说的流程,添加到了Vue的原型上面,对外暴露了几个API,$on
,$once
,$off
,#emit
。
$on
方法,过滤掉了钩子函数中的方法。
$off
方法,当事件是字符串时,将对应的回调设置为null,当为数组时,将对应的函数从缓存列表中删除。
$emit
就不用说了,从缓存列表中找到对应的函数去执行。
源码如下:
export function eventsMixin (Vue: Class<Component>) { 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 } 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++) { this.$off(event[i], fn) } return vm } // specific event const cbs = vm._events[event] if (!cbs) { return vm } if (arguments.length === 1) { 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 } }
今日总结
开卷有益,书要多读,细度。勤于思考总结。
最后说两句
- 动一动您的小手,
「点个赞吧」
- 都看到这里了,不妨
「加个关注」