我们平时开发工作中,处理组件间的通讯,原生的交互,都离不开事件。对于一个组件元素,我们不仅仅可以绑定原生的 DOM 事件,还可以绑定自定义事件,非常灵活和方便。那么接下来我们从源码角度来看看它的实现原理。
为了更加直观,我们通过一个例子来分析它的实现:
编译
先从编译阶段开始看起,在 阶段,会执行 方法,它的定义在 中:
在对标签属性的处理过程中,判断如果是指令,首先通过 解析出修饰符,然后判断如果事件的指令,则执行 方法,它的定义在 中:
函数看起来长,实际上就做了 3 件事情,首先根据 修饰符对事件名 做处理,接着根据 判断是一个纯原生事件还是普通事件,分别对应 和 ,最后按照 对事件做归类,并把回调函数的字符串保留到对应的事件中。在我们的例子中,父组件的 节点生成的 和 如下:
子组件的 节点生成的 如下:
然后在 的阶段,会在 函数中根据 AST 元素节点上的 和 生成 数据,它的定义在 中:
对于这两个属性,会调用 函数,定义在 中:
方法遍历事件对象 ,对同一个事件名称的事件调用 方法,它的内容看起来多,但实际上逻辑很简单,首先先判断如果 是一个数组,就遍历它然后递归调用 方法并拼接结果,然后判断 是一个函数的调用路径还是一个函数表达式, 接着对 做判断,对于没有 的情况,就根据 不同情况处理,要么直接返回,要么返回一个函数包裹的表达式;对于有 的情况,则对各种不同的 情况做不同处理,添加相应的代码串。那么对于我们的例子而言,父组件生成的 串为:
子组件生成的 串为:
那么到这里,编译部分完了,接下来我们来看一下运行时部分是如何实现的。其实 Vue 的事件有 2 种,一种是原生 DOM 事件,一种是用户自定义事件,我们分别来看。
DOM 事件
还记得我们之前在 的时候执行各种 的钩子函数吗,当时这部分是略过的,我们之前只分析了 DOM 是如何渲染的,而 DOM 元素相关的属性、样式、事件等都是通过这些 的钩子函数完成设置的。所有和 web 相关的 都定义在 目录下,我们这次只关注目录下的 即可。在 过程中的创建阶段和更新阶段都会执行 :
首先获取 ,这就是我们之前的生成的 中对应的事件对象, 是当前 对于的 DOM 对象, 主要是对 相关的处理,我们之后分析 的时候会介绍,接着调用 方法,它的定义在 中:
的逻辑很简单,遍历 去添加事件监听,遍历 去移除事件监听,关于监听和移除事件的方法都是外部传入的,因为它既处理原生 DOM 事件的添加删除,也处理自定义事件的添加删除。对于 的遍历,首先获得每一个事件名,然后做 的处理:
根据我们的的事件名的一些特殊标识(之前在 的时候添加上的)区分出这个事件是否有 、、 等修饰符。处理完事件名后,又对事件回调函数做处理,对于第一次,满足 并且 ,会执行 方法去创建一个回调函数,然后在执行 完成一次事件绑定。我们先看一下 的实现:
这里定义了 方法并返回,由于一个事件可能会对应多个回调函数,所以这里做了数组的判断,多个回调函数就依次调用。注意最后的赋值逻辑, ,每一次执行 函数都是从 里取执行的回调函数,回到 ,当我们第二次执行该函数的时候,判断如果 ,那么只需要更改 把之前绑定的 赋值为新的回调函数即可,并且 通过 保留引用关系,这样就保证了事件回调只添加一次,之后仅仅去修改它的回调函数的引用。 函数的最后遍历 拿到事件名称,判断如果满足 ,则执行 去移除事件回调。了解了 的实现后,我们来看一下在原生 DOM 事件中真正添加回调和移除回调函数的实现,它们的定义都在 中:
和 的逻辑很简单,就是实际上调用原生 和 ,并根据参数传递一些配置,注意这里的 会用 包裹一下,它的定义在 中:
实际上就是强制在 DOM 事件的回调函数执行期间如果修改了数据,那么这些数据更改推入的队列会被当做 在 后执行。
自定义事件
除了原生 DOM 事件,Vue 还支持了自定义事件,并且自定义事件只能作用在组件上,如果在组件上使用原生事件,需要加 修饰符,普通元素上使用 修饰符无效,接下来我们就来分析它的实现。在 阶段,如果是一个组件节点,则通过 创建一个组件 ,我们再来回顾这个方法,定义在 中:
我们只关注事件相关的逻辑,可以看到,它把 赋值给了 ,把 赋值给了 ,这样所有的原生 DOM 事件处理跟我们刚才介绍的一样,它是在当前组件环境中处理的。而对于自定义事件,我们把 作为 的 传入,它是在子组件初始化阶段中处理的,所以它的处理环境是子组件。然后在子组件的初始化的时候,会执行 方法,它的定义在 中:
这里拿到了父组件传入的 ,然后在执行 的过程中,会处理这个 ,定义在 中:
拿到 后,执行 方法:
我们之前介绍过,所以对于自定义事件和原生 DOM 事件处理的差异就在事件添加和删除的实现上,来看一下自定义事件 和 的实现:
实际上是利用 Vue 定义的事件中心,简单分析一下它的实现:
非常经典的事件中心的实现,把所有的事件用 存储起来,当执行 的时候,按事件的名称 把回调函数 存储起来 。当执行 的时候,根据事件名 找到所有的回调函数 ,然后遍历执行所有的回调函数。当执行 的时候会移除指定事件名 和指定的 当执行 的时候,内部就是执行 ,并且当回调函数执行一次后再通过 移除事件的回调,这样就确保了回调函数只执行一次。所以对于用户自定义的事件添加和删除就是利用了这几个事件中心的 API。需要注意的事一点, 是给当前的 上派发的实例,之所以我们常用它做父子组件通讯,是因为它的回调函数的定义是在父组件中,对于我们这个例子而言,当子组件的 被点击了,它通过 派发事件,那么子组件的实例就监听到了这个 事件,并执行它的回调函数——定义在父组件中的 方法,这样就相当于完成了一次父子组件的通讯。
总结
那么至此我们对 Vue 的事件实现有了进一步的了解,Vue 支持 2 种事件类型,原生 DOM 事件和自定义事件,它们主要的区别在于添加和删除事件的方式不一样,并且自定义事件的派发是往当前实例上派发,但是可以利用在父组件环境定义回调函数来实现父子组件的通讯。另外要注意一点,只有组件节点才可以添加自定义事件,并且添加原生 DOM 事件需要使用 修饰符;而普通元素使用 修饰符是没有作用的,也只能添加原生 DOM 事件。
领取专属 10元无门槛券
私享最新 技术干货