vue源码解析之事件机制原理(4)

整段代码关于事件核心操作:

var listeners = data.on;//listeners缓存data.on的函数。这里就是componenton事件
// replace with listeners with .native modifier
data.on = data.nativeOn;//正常的data.on会被native修饰符的事件所替换

经过这两句话。.native修饰符的事件会被放在data.on上面。接下来data.on上的事件(这里就是nativeclick)会按普通的html事件往下走。最后执行target.add('',''')挂上原生的事件。而先前的data.on上的被缓存在listeneners的事件就没着么愉快了。接下来他会在组件init的时候。它会进入一下分支:

function initEvents (vm) {
 vm._events = Object.create(null);
 vm._hasHookEvent = false;
 // init parent attached events
 var listeners = vm.$options._parentListeners;
 if (listeners) {
 updateComponentListeners(vm, listeners);
 }
}

function updateComponentListeners (
 vm,
 listeners,
 oldListeners
) {
 target = vm;
 updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
}

function add (event, fn, once$$1) {
 if (once$$1) {
 target.$once(event, fn);
 } else {
 target.$on(event, fn);
 }
}

发现组件上的没有.native的修饰符调用的是$on方法。这个好熟悉。进入到$on,$emit大致想到是一个典型的观察者模式的事件。看看相关$on,$emit代码。我加点注解:

Vue.prototype.$on = function (event, fn) {
 var this$1 = this;

 var vm = this;
 if (Array.isArray(event)) {
  for (var i = 0, l = event.length; i < l; i++) {
  this$1.$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;
 console.log(vm);
 {
  var lowerCaseEvent = event.toLowerCase();
  if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
  tip(
   "Event \"" + lowerCaseEvent + "\" is emitted in component " +
   (formatComponentName(vm)) + " but the handler is registered for \"" + 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 \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
  );
  }
 }
 var cbs = vm._events[event];
 console.log(cbs);
 if (cbs) {
  cbs = cbs.length > 1 ? toArray(cbs) : cbs;
  var args = toArray(arguments, 1);
  for (var i = 0, l = cbs.length; i < l; i++) {
  cbs[i].apply(vm, args);//当emit的时候调用该事件。注意上面说的vue在初始化的守候。用bind修饰了事件函数。所以组件上挂载的事件都是在父作用域中的
  }
 }
 return vm
 };

看了上面的on,emit用法下面这个demo也就瞬间秒解了(一个经常用的非父子组件通信):