因为对 Vue.js 很感兴趣,而且平时工作的技术栈也是 Vue.js ,这几个月花了些时间研究学习了一下 Vue.js 源码,并做了总结与输出。 文章的原地址:https://github.com/answershuto/learnVue。 在学习过程中,为 Vue 加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习 Vue 源码的小伙伴有所帮助。 可能会有理解存在偏差的地方,欢迎提 issue 指出,共同学习,共同进步。
众所周知,Vue.js 为我们提供了四个事件 API,分别是$on,$once,$off,[$emit]( https://cn.vuejs.org/v2/api/#vm-emit-event-… args)。
初始化事件在 vm 上创建一个_events 对象,用来存放事件。_events 的内容如下:
{ eventName: [func1, func2, func3] }
存放事件名以及对应执行方法。
/*初始化事件*/ export function initEvents (vm: Component) { /*在 vm 上创建一个_events 对象,用来存放事件。*/ vm._events = Object.create(null) /*这个 bool 标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/ vm._hasHookEvent = false // init parent attached events /*初始化父组件 attach 的事件*/ const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } }
$on 方法用来在 vm 实例上监听一个自定义事件,该事件可用$emit 触发。
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++) { 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 /*这里在注册事件的时候标记 bool 值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/ if (hookRE.test(event)) { vm._hasHookEvent = true } } return 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 }
$off 用来移除自定义事件
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 /*如果 event 是数组则递归注销事件*/ 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] /*Github:https://github.com/answershuto*/ /*本身不存在该事件则直接返回*/ if (!cbs) { return vm } /*如果只传了 event 参数则注销该 event 方法下的所有方法*/ if (arguments.length === 1) { vm._events[event] = null return vm } // 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 }
$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) /*遍历执行*/ for (let i = 0, l = cbs.length; i < l; i++) { cbs[i].apply(vm, args) } } return vm }
作者:染陌
Email: [email protected] or [email protected]
Github: https://github.com/answershuto
Blog:http://answershuto.github.io/
知乎主页:https://www.zhihu.com/people/cao-yang-49/activities
知乎专栏:https://zhuanlan.zhihu.com/ranmo
掘金: https://juejin.im/user/58f87ae844d9040069ca7507
osChina:https://my.oschina.net/u/3161824/blog
转载请注明出处,谢谢。
欢迎关注我的公众号
![]() | 1 blanu 2017-09-27 22:34:39 +08:00 via iPhone 我觉得这是非常无聊的文章 |
2 fuermosi777 2017-09-28 00:06:49 +08:00 我同意一楼的看法 |
![]() | 3 Lisp 2017-09-28 00:30:16 +08:00 via iPhone 下拉最后,并不是好心的分享 |