概述
之前在和朋友讨论传值方式的时候,发现他对 $on 和 $emit 的理解很混乱,他认为 $emit 会向父组件抛出事件,而 $on 会接收子组件抛来的事件,事实上这个理解是错误的。
先说结论,$emit 也只会在当前实例触发事件,$on 也只会捕获当前实例的事件。
$on 和 $emit 的运行机制
先看一段源代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14Vue.prototype.$emit = function (event) {
var vm = this;
// 省略一系列对事件名称格式的判断代码
var cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
var info = "event handler for \"" + event + "\"";
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm
};
这是 $emit
函数的实现,首先是通过事件名 event 在 _events
中找到事件的回调函数,如果回调函数存在,那么就执行回调函数。
在这个过程中 vm 为 this,也就是说在组件中使用 this.$emit()
的时候,vm 是指向组件本身的。
那么 _events
属性是从哪来的呢?接着看一段 $on
的源代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Vue.prototype.$on = function (event, fn) {
var vm = this;
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$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
};
关键在 else 块中的第一行,当调用$on
函数时,会将事件名 event 作为一个键,放入_events
对象中,值为回调函数组成的数组(因为可以多次使用 $on
注册事件回调,所以回调函数都会被存入数组中,按注册顺序执行)。
概括
避免有人觉得“太长不看”,这里做一个简单概括:
$on
的功能是在实例上注册事件的回调函数,形式为 事件: 回调数组
,回调函数会被放入当前实例 vm的 _events
对象中。$emit
的功能是根据参数的事件名,执行相对应的回调函数或回调数组,它会在当前实例 vm的 _events
对象中找事件对应的回调函数去执行。
所以$emit
和$on
这两个函数涉及的对象都是 vm,和父组件毫无关系。
关于v-on
提到 \$emit 和 \$on,那就不能少了 v-on,它也是 Vue 事件机制中重要的一环。
首先,v-on
指令是放在元素或组件中的,在指令的定义函数中参数 el
指向的是指令挂载的组件或元素的。
v-on:click="handleClick"
这种写法会将 handleClick
函数作为 binding.value
传给指令定义函数。
那么明确了这两点之后,来看一段代码:
1 | <template> |
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
这是组件 parent 的模版,结合官方文档中的这段话,那就能确定这里的 handleClick
函数是来自于组件parent的(使用 parent 的作用域),组件 child 上的 v-on:click
会监听 child 的事件 click(因为 el 指向 v-on 挂载的组件),所以v-on:click="handleClick"
这个语句的意思实际上是:
监听 child 组件上的click事件,事件触发后调用 parent 提供的 handleClick 函数
而不应该被理解成组件 parent 去监听 child 的 click 事件,满足条件就触发 handleClick。
再进一步
结合$emit
的行为来看 v-on,v-on 实际上是将父组件提供的回调函数 handleClick 放入当前组件 vm(也就是 Child)的_events
中,所以 vm.$emit()
,也能找到父组件传入的 handleClick
函数并执行。
1 | const Parent = new Vue({ |
打印 Parent.$children[0]._events
的结果为{ click: Array(1) }
,证明 handleClick
已经传递给 Child 了,Child.$emit(‘click’),一样能触发 handleClick
函数。