好家伙,

0、一个例子

<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<title>Vue 父子组件通信示例</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<parent-component></parent-component>
</div>

<script>
//子组件
Vue.component('child-component', {
template: `
<div>
<button @click="sendDataToParent">发送数据给父组件</button>
</div>
`,
methods: {
sendDataToParent() {
this.$emit('data-sent', '这是从子组件发送的数据');
}
}
});
//父组件
Vue.component('parent-component', {
template: `
<div>
<child-component @data-sent="handleDataReceived"></child-component>
<p>从子组件接收到的数据:{{ receivedData }}</p>
</div>
`,
data() {
return{
receivedData:
''};
},
methods: {
handleDataReceived(data) {
this.receivedData =data;
}
}
});
//创建Vue实例
let vm = newVue({
el:
'#app'});</script>
</body>

</html>

1、$emit、$on源码

源码实现,我们来看$emit、$on的源码实现部分

Vue.prototype.$on = function(event, fn) {var vm = this;if(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;
}
}
returnvm;
};

Vue.prototype.$emit
= function(event) {var vm = this;//处理大小写
{var lowerCaseEvent =event.toLowerCase();if (lowerCaseEvent !== event &&vm._events[lowerCaseEvent]) {
tip(
"Event \"".concat(lowerCaseEvent, "\" is emitted in component ") +
"".concat(formatComponentName(vm), " but the handler is registered for \"").concat(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 \"".concat(hyphenate(event), "\" instead of \"").concat(event, "\"."));
}
}
var cbs =vm._events[event];if(cbs) {
cbs
= cbs.length > 1 ?toArray(cbs) : cbs;var args = toArray(arguments, 1);var info = "event handler for \"".concat(event, "\"");for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
returnvm;
};
functioninvokeWithErrorHandling(handler, context, args, vm, info) {varres;try{
res
= args ?handler.apply(context, args) : handler.call(context);if (res && !res._isVue && isPromise(res) && !res._handled) {
res.
catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
res._handled
= true;
}
}
catch(e) {
handleError(e, vm, info);
}
returnres;
}

2.代码解释

看着比较复杂,所以我们精简一下,去掉性能优化和一些正则表达式还有一些数组处理

精简下来无非几句代码

$on

(vm._events[event] || (vm._events[event] = [])).push(fn);

$emit

var cbs =vm._events[event];

invokeWithErrorHandling(cbs[i], vm, args, vm, info);
functioninvokeWithErrorHandling(handler, context, args, vm, info) {

res
= args ?handler.apply(context, args) : handler.call(context);returnres;

}

分析:

$emit、$on的实现使用了观察者模式的设计思想

$on
方法用于在当前Vue实例上注册事件监听器。

vm._events
:维护一个事件与其处理函数的映射。每个事件对应一个数组,数组中存放了所有注册的处理函数。

$emit
方法用于触发事件,当事件被触发时,调用所有注册在该事件上的处理函数。

非常简单

3.源码注释版本

//在Vue的原型上定义一个方法$on
Vue.prototype.$on = function(event, fn) {//vm指的是Vue的实例
var vm = this;//如果event是一个数组,那么对每个事件递归调用$on方法
if(isArray(event)) {for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
}
//如果event不是一个数组,那么将函数fn添加到vm._events[event]中
else{
(vm._events[event]
|| (vm._events[event] =[])).push(fn);//如果event是一个钩子事件,那么设置vm._hasHookEvent为true
if(hookRE.test(event)) {
vm._hasHookEvent
= true;
}
}
//返回Vue的实例
returnvm;
};
//在Vue的原型上定义一个方法$emit
Vue.prototype.$emit = function(event) {//vm指的是Vue的实例
var vm = this;//处理事件名的大小写
{var lowerCaseEvent =event.toLowerCase();//如果事件名的小写形式和原事件名不同,并且vm._events中有注册过小写的事件名
if (lowerCaseEvent !== event &&vm._events[lowerCaseEvent]) {//那么提示用户事件名的大小写问题
tip("Event \"".concat(lowerCaseEvent, "\" is emitted in component ") +
"".concat(formatComponentName(vm), " but the handler is registered for \"").concat(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 \"".concat(hyphenate(event), "\" instead of \"").concat(event, "\"."));
}
}
//获取vm._events[event]中的所有回调函数
var cbs =vm._events[event];//如果存在回调函数
if(cbs) {//如果回调函数的数量大于1,那么将其转换为数组
cbs = cbs.length > 1 ?toArray(cbs) : cbs;//获取除event外的其他参数
var args = toArray(arguments, 1);//定义错误处理信息
var info = "event handler for \"".concat(event, "\"");//对每个回调函数进行错误处理
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
//返回Vue的实例
returnvm;
};
//定义一个错误处理函数
functioninvokeWithErrorHandling(handler, context, args, vm, info) {varres;try{//如果存在参数args,那么使用apply方法调用handler,否则使用call方法调用handler
res = args ?handler.apply(context, args) : handler.call(context);//如果返回结果res存在,且res不是Vue实例,且res是一个Promise,且res没有被处理过
if (res && !res._isVue && isPromise(res) && !res._handled) {//那么对res进行错误处理,并标记res已经被处理过
res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
res._handled
= true;
}
}
//如果在执行过程中抛出错误,那么进行错误处理
catch(e) {
handleError(e, vm, info);
}
//返回结果res
returnres;
}

标签: none

添加新评论