- A+
所属分类:Web前端
好家伙,
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 = new Vue({ 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; } } return vm; }; 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); } } return vm; }; function invokeWithErrorHandling(handler, context, args, vm, info) { var res; 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); } return res; }
2.代码解释
看着比较复杂,所以我们精简一下,去掉性能优化和一些正则表达式还有一些数组处理
精简下来无非几句代码
$on
(vm._events[event] || (vm._events[event] = [])).push(fn);
$emit
var cbs = vm._events[event]; invokeWithErrorHandling(cbs[i], vm, args, vm, info); function invokeWithErrorHandling(handler, context, args, vm, info) { res = args ? handler.apply(context, args) : handler.call(context); return res; }
分析:
$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的实例 return vm; }; // 在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的实例 return vm; }; // 定义一个错误处理函数 function invokeWithErrorHandling(handler, context, args, vm, info) { var res; 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 return res; }