找回密码
 立即注册
首页 业界区 业界 Vue源码学习(二十):$emit、$on实现原理

Vue源码学习(二十):$emit、$on实现原理

骛扼铮 2025-6-6 15:32:12
好家伙,
 
0、一个例子
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>Vue 父子组件通信示例</title>
  6.    
  7. </head>
  8. <body>
  9.    
  10.         <parent-component></parent-component>
  11.    
  12.    
  13. </body>
  14. </html>
复制代码
 
 
1、$emit、$on源码

源码实现,我们来看$emit、$on的源码实现部分
  1. Vue.prototype.$on = function (event, fn) {
  2.     var vm = this;
  3.     if (isArray(event)) {
  4.         for (var i = 0, l = event.length; i < l; i++) {
  5.             vm.$on(event[i], fn);
  6.         }
  7.     }
  8.     else {
  9.         (vm._events[event] || (vm._events[event] = [])).push(fn);
  10.         // optimize hook:event cost by using a boolean flag marked at registration
  11.         // instead of a hash lookup
  12.         if (hookRE.test(event)) {
  13.             vm._hasHookEvent = true;
  14.         }
  15.     }
  16.     return vm;
  17. };
  18. Vue.prototype.$emit = function (event) {
  19.     var vm = this;
  20.     // 处理大小写
  21.     {
  22.         var lowerCaseEvent = event.toLowerCase();
  23.         if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
  24.             tip("Event "".concat(lowerCaseEvent, "" is emitted in component ") +
  25.                 "".concat(formatComponentName(vm), " but the handler is registered for "").concat(event, "". ") +
  26.                 "Note that HTML attributes are case-insensitive and you cannot use " +
  27.                 "v-on to listen to camelCase events when using in-DOM templates. " +
  28.                 "You should probably use "".concat(hyphenate(event), "" instead of "").concat(event, ""."));
  29.         }
  30.     }
  31.     var cbs = vm._events[event];
  32.     if (cbs) {
  33.         cbs = cbs.length > 1 ? toArray(cbs) : cbs;
  34.         var args = toArray(arguments, 1);
  35.         var info = "event handler for "".concat(event, """);
  36.         for (var i = 0, l = cbs.length; i < l; i++) {
  37.             invokeWithErrorHandling(cbs[i], vm, args, vm, info);
  38.         }
  39.     }
  40.     return vm;
  41. };
  42. function invokeWithErrorHandling(handler, context, args, vm, info) {
  43.     var res;
  44.     try {
  45.         res = args ? handler.apply(context, args) : handler.call(context);
  46.         if (res && !res._isVue && isPromise(res) && !res._handled) {
  47.             res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
  48.             res._handled = true;
  49.         }
  50.     }
  51.     catch (e) {
  52.         handleError(e, vm, info);
  53.     }
  54.     return res;
  55. }
复制代码
 
2.代码解释

看着比较复杂,所以我们精简一下,去掉性能优化和一些正则表达式还有一些数组处理
精简下来无非几句代码
 
$on
  1. (vm._events[event] || (vm._events[event] = [])).push(fn);
复制代码
$emit
  1. var cbs = vm._events[event];
  2. invokeWithErrorHandling(cbs[i], vm, args, vm, info);
  3. function invokeWithErrorHandling(handler, context, args, vm, info) {
  4.    
  5.         res = args ? handler.apply(context, args) : handler.call(context);
  6.         return res;
  7. }
复制代码
 
分析:
$emit、$on的实现使用了观察者模式的设计思想
$on方法用于在当前Vue实例上注册事件监听器。
vm._events:维护一个事件与其处理函数的映射。每个事件对应一个数组,数组中存放了所有注册的处理函数。
$emit方法用于触发事件,当事件被触发时,调用所有注册在该事件上的处理函数。
 
非常简单
 
3.源码注释版本
  1. // 在Vue的原型上定义一个方法$on
  2. Vue.prototype.$on = function (event, fn) {
  3.     // vm指的是Vue的实例
  4.     var vm = this;
  5.     // 如果event是一个数组,那么对每个事件递归调用$on方法
  6.     if (isArray(event)) {
  7.         for (var i = 0, l = event.length; i < l; i++) {
  8.             vm.$on(event[i], fn);
  9.         }
  10.     }
  11.     // 如果event不是一个数组,那么将函数fn添加到vm._events[event]中
  12.     else {
  13.         (vm._events[event] || (vm._events[event] = [])).push(fn);
  14.         // 如果event是一个钩子事件,那么设置vm._hasHookEvent为true
  15.         if (hookRE.test(event)) {
  16.             vm._hasHookEvent = true;
  17.         }
  18.     }
  19.     // 返回Vue的实例
  20.     return vm;
  21. };
  22. // 在Vue的原型上定义一个方法$emit
  23. Vue.prototype.$emit = function (event) {
  24.     // vm指的是Vue的实例
  25.     var vm = this;
  26.     // 处理事件名的大小写
  27.     {
  28.         var lowerCaseEvent = event.toLowerCase();
  29.         // 如果事件名的小写形式和原事件名不同,并且vm._events中有注册过小写的事件名
  30.         if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
  31.             // 那么提示用户事件名的大小写问题
  32.             tip("Event "".concat(lowerCaseEvent, "" is emitted in component ") +
  33.                 "".concat(formatComponentName(vm), " but the handler is registered for "").concat(event, "". ") +
  34.                 "Note that HTML attributes are case-insensitive and you cannot use " +
  35.                 "v-on to listen to camelCase events when using in-DOM templates. " +
  36.                 "You should probably use "".concat(hyphenate(event), "" instead of "").concat(event, ""."));
  37.         }
  38.     }
  39.     // 获取vm._events[event]中的所有回调函数
  40.     var cbs = vm._events[event];
  41.     // 如果存在回调函数
  42.     if (cbs) {
  43.         // 如果回调函数的数量大于1,那么将其转换为数组
  44.         cbs = cbs.length > 1 ? toArray(cbs) : cbs;
  45.         // 获取除event外的其他参数
  46.         var args = toArray(arguments, 1);
  47.         // 定义错误处理信息
  48.         var info = "event handler for "".concat(event, """);
  49.         // 对每个回调函数进行错误处理
  50.         for (var i = 0, l = cbs.length; i < l; i++) {
  51.             invokeWithErrorHandling(cbs[i], vm, args, vm, info);
  52.         }
  53.     }
  54.     // 返回Vue的实例
  55.     return vm;
  56. };
  57. // 定义一个错误处理函数
  58. function invokeWithErrorHandling(handler, context, args, vm, info) {
  59.     var res;
  60.     try {
  61.         // 如果存在参数args,那么使用apply方法调用handler,否则使用call方法调用handler
  62.         res = args ? handler.apply(context, args) : handler.call(context);
  63.         // 如果返回结果res存在,且res不是Vue实例,且res是一个Promise,且res没有被处理过
  64.         if (res && !res._isVue && isPromise(res) && !res._handled) {
  65.             // 那么对res进行错误处理,并标记res已经被处理过
  66.             res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
  67.             res._handled = true;
  68.         }
  69.     }
  70.     // 如果在执行过程中抛出错误,那么进行错误处理
  71.     catch (e) {
  72.         handleError(e, vm, info);
  73.     }
  74.     // 返回结果res
  75.     return res;
  76. }
复制代码
 
 
 
 
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册