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

  • Vue源码学习(二十):$emit、$on实现原理已关闭评论
  • 44 次浏览
  • A+
所属分类:Web前端
摘要

好家伙,    源码实现,我们来看$emit、$on的源码实现部分  看着比较复杂,所以我们精简一下,去掉性能优化和一些正则表达式还有一些数组处理

好家伙,

 

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; }