写文章不容易,点个赞呗兄弟 专一 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工做原理,源码版助于了解内部详情,让咱们一块儿学习吧 研究基于 Vue版本 【2.5.17】node
若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧函数
【Vue原理】Event - 源码版 之 绑定组件自定义事件 学习
组件自定义事件实际上是我最感兴趣的,我当时花了好多时间去探索的哈哈哈,探索完了以后,发现很简单的,能够先看下白话版了解下this
【Vue原理】Event - 白话版 prototype
我当时脑海中就几个问题,我很想弄懂啊3d
一、父给子绑定的事件,存放在父组件仍是子组件? 二、父给子绑定自定义事件,子组件为何能够触发? 三、子组件触发事件后,是怎么调用绑定的 父组件的方法的?
看看当时作的笔记时间,已通过了很久了啊code
笔记看着很混乱,因此下定决心写文章,写得详详细细的,然本身一眼就明白,并且怕之后本身忘记component
首先确定是为本身服务的,只是顺便分享给你们,能帮到别人少走弯路而已哈哈哈对象
好的,很少说了,立刻进入主题~~看完文章的欢迎到下面投票啊啊啊啊blog
一样,一个给组件绑定自定义事件的模板
而后template 解析成下面的渲染函数
渲染函数执行,生成这样的组件外壳VNode
还能够打印组件实例看一下
你能够看到,绑定的自定义事件,存在了 组件外壳VNode的 componentOptions.listeners 中
等下!
渲染函数中,明明把事件解析放在 on 的啊,怎么到 listeners了
这里记录一下哈
当 _c('test') 执行的时候,由于是组件,因此内部会特别调用 createComponent 去生成组件的VNode
而这个VNode 是外壳VNode
下面的源码中能够很清楚的看到
一、把 on 赋值给了 listeners
二、listeners 传给了 VNode 构造函数,保存到了 vnode.componentOptions
function createComponent( Ctor, data, context, children, tag ) { var listeners = data.on; data.on = data.nativeOn; var vnode = new VNode( tag, // 组件名字 data, undefined, undefined, undefined, context, // 下面这个对象就存在 vnode.componentOptions { listeners: listeners, } ); return vnode } function VNode( tag, data, children, text, elm, context, componentOptions ) { this.componentOptions = componentOptions; };
想了解多一点Vnode能够看 VNode - 源码版
想了解 component 流程的,能够看 Component - 白话版
因此第一个问题获得答案,父给子绑定的事件,存放在子组件中!
好的,模板上的事件已经被解析并保存好了
接下来,就轮到 事件的注册 showtime
这个事情,发生在建立组件实例的时候
若是你要问,具体是怎么到了建立实例这里的话,你能够看下面两篇文章,不过看你有没有这个耐心了(学习的事,能不要耐心吗)
要不就看白话版吧
组件实例初始化会调用 Vue.prototype._init 没错了
而后 _init 会调用一个 initEvents 的东东去进行初始化事件对象,以下
Vue.prototype._init = function(options) { // 增长组件选项,详情往下看 initInternalComponent(vm,options); initEvents(vm); ....处理选项数据 }
initEvents:没错就是我!
initEvent 这个函数作了什么事情呢?
一、给实例上添加一个 _event 对象,用于保存自定义事件
二、获取到 父组件给 子组件绑定的自定义事件(不懂就接着往下看)
三、调用 updateComponentListeners 开始注册
function initEvents(vm) { vm._events = Object.create(null); var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } }
有个疑惑
_parentListeners 保存的是什么东西?父组件给子组件的事件?
百思不得其解,诶,看到_init 中有一句代码调用initInternalComponent,也许就是这个函数搞的鬼,咱们来看下
function initInternalComponent(vm, options) { .....保存节点等信息 // _parentVnode 是外壳节点 var parentVnode = options._parentVnode; // 保存父组件给子组件关联的数据 var vcp= parentVnode.componentOptions; vm.$options._parentListeners =vcp.listeners; ....保存渲染函数 }
这个函数的做用是初始化一些组件的信息,包括转移一些数据
哦~~原来就是经过这个函数偷偷摸摸转移了 listeners 到了 vm.$option._parentListeners
前面说过【父给子绑定的事件】解析存放在 外壳节点vnode.componentOptions.listeners 中(不明白能够翻到上面的<怎么解析>部分),而后转移的就是这个 listeners
解决这个疑惑了,好的,咱们接着来走 initEvent 剩下的流程把~~
来看下 updateComponentListeners
function updateComponentListeners( vm, listeners, oldListeners ) { var name, cur, old; for (name in listeners) { cur = listeners[name]; old = oldListeners[name]; // 没有旧事件,就直接添加新事件 if (typeof old === "undefined") { vm.$on(name, cur); } // 新事件和旧事件不同,替换旧事件 else if (cur !== old) { on[name] = cur; } } // 移除旧事件 for (name in oldListeners) { if (typeof listeners[name] === "undefined") { vm.$off(name, oldOn[name]); } } }
就是从他这里开始注册事件的
彷佛没什么好说的,注意一点
绑定和解绑事件,是直接调用 Vue 的自定义事件方法 $on 和 $off ,你们是否是很熟悉???
没错,在这篇文章中说过
这就解释咱们开篇第二个问题了!!!!
为何我给子组件绑定自定义事件,能够在子组件像下面这样触发?
this.$emit('test')
由于 组件绑定的自定义事件 和 Vue 的自定义事件
两种事件都是使用同一种方法注册的,因此都存在一样一个事件对象 【vm._events】 中,是同样操做的流程
就是这样的
能够看到组件实例上的 _events
而且知道了第三个问题,怎么调用到父组件的方法?
由于给子组件注册事件的时候,直接存放父组件的回调,因此触发事件会调用到父组件的方法
而后你还能够问出一个问题!
为何子组件触发事件以后,调用父组件的方法,而父组件的方法上下文对象仍是父组件
哈哈,由于 methods 方法已经使用 bind 绑定啦,上下文对象固定了为父组件实例的,因此无论谁调用,怎么调用,都是父组件
详情能够看这里,