首先总体介绍下这四个方法实现的基本原理:
1、vue实例上会创建一个对象来保存所有要监听的事件:vm._events = {}
2、每当我们要监听一个事件,就往vm._events里添加一个键值对,事件的名称作为键,一个空数组作为值。例如我们要监听的事件名称为
event1
,则
vm._events = {event1: []}
3、监听事件的回调函数都会添加到对应的数组中,例如我们调用
vm.$on('event1', cb1);
vm.$on('event1', cb2);
vm.$on('event1', cb3);
复制代码
则此时
vm._events={event1: [cb1, cb2, cb3]}
4、当调用移除监听事件的方法时,所做的操作为移除其对应数组里的回调函数,例如当我们调用
vm.$off('event1', cb1);
复制代码
这时cb1就被移除了,
vm._events={event1: [cb2, cb3]}
5、当我们执行
$emit
触发对应事件时,所做的操作就是把该事件对应数组里的回调函数都拿出来执行一遍,例如当我们调用
vm.$emit('event')
复制代码
这时候会取出
event1
对应数组里的
cb2
和
cb3
执行
6、
$once
表示该事件只会触发执行一次,后面在触发就没用了,例如当我们调用:
// 用$on方法监听event2,回调函数为cb4
vm.$on('event2', cb4);
// 用$once方法监听event2,回调函数为cb5
vm.$once('event2', cb5);
// 触发event2事件,会执行cb4和cb5
vm.$emit('event2');
// 再次触发event2事件,这里只会执行cb4,不会执行cb5,cb5只会执行一次
vm.$emit('event2');
复制代码
以上即为事件派发方法的基本原理,当然这些方法还有一些稍微复杂一点的使用方式,比如
$on(['event1', 'event2'], cb) // 监听多个事件
$off(['event1', 'event2'], cb) // 移除多个事件
$off() // 移除事件不传参数
$off('event1') // 移除事件传一个参数
$off('event1', cb) // 移除事件传两个参数
$emit('event1', param1, param2) // 触发事件传参数
看完下面的源码解析就知道这些情况都是怎么处理的了
下面来看下vue源码里这四个方法的具体实现:
Vue.prototype.$on = function (event, fn) {
const vm = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
return vm
复制代码
$off:
Vue.prototype.$off = function (event, fn) {
const vm = this
if (!arguments.length) {
vm._events = Object.create(null)
return vm
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
return vm
const cbs = vm._events[event]
if (!cbs) {
return vm
if (!fn) {
vm._events[event] = null
return vm
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) {
const vm = this
let cbs = vm._events[event]
if (cbs) {
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
return vm
复制代码
$once:
Vue.prototype.$once = function (event, fn) {
const vm = this
// 封装一个高阶函数on,在on里面调用fn
function on () {
// 每当执行了一次on,移除event里的on事件,后面再触发event事件就不会再执行on事件了,也就不会执行on里面的fn事件
vm.$off(event, on)
// 执行on的时候,执行fn函数
fn.apply(vm, arguments)
// 这个赋值是在$off方法里会用到的
// 比如我们调用了vm.$off(fn)来移除fn回调函数,然而我们在调用$once的时候,实际执行的是vm.$on(event, on)
// 所以在event的回调函数数组里添加的是on函数,这个时候要移除fn,我们无法在回调函数数组里面找到fn函数移除,只能找到on函数
// 我们可以通过on.fn === fn来判断这种情况,并在回调函数数组里移除on函数
on.fn = fn
// $once最终调用的是$on,并且回调函数是on
vm.$on(event, on)
return vm
复制代码