VUE源码分析之子组件向父组件通过$emit传递数据过程

还是从一个简单例子看下这个过程:



                @child-event = "'handleChildEvent'">
   

 

例子也非常简单,子组件通过$emit方法向父组件发送child-event消息,父组件监听此child-event消息。从我们前面几篇分析文章中我们可以推测出$emit 应该是Vue对象的原型对象方法,在Vue.js中搜索Vue.prototype.$emit 看看,果然有这个方法:

    

    Vue.prototype.$emit = function (event) {
      var vm = this;
      {
        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];
      if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
        var args = toArray(arguments, 1);
        var info = "event handler for \"" + event + "\"";
        for (var i = 0, l = cbs.length; i < l; i++) {
          invokeWithErrorHandling(cbs[i], vm, args, vm, info);
        }
      }
      return vm
    };

从var cbs = vm._events[event];这行代码中我们可以看出是从_event数组中根据event找到对应的cbs回调函数,接下来就执行这个回调函数。那么什么时候注册的回调函数呢?我们接着在Vue.js中搜索_event数组,在Vue.prototype.$on方法中找到了:

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

是这一行代码:(vm._events[event] || (vm._events[event] = [])).push(fn);  往_event数组中push回调函数。我们接着往上找调用这个$on方法的地方。能猜测到应该是在解析模板后创建子组件VNode的过程中。我们通过打印先看一下解析模板后生成的render函数:

with(this){return _c('div',{attrs:{"id":"app"}},[_c('child-comp',{on:{"child-event":handleChildEvent}})],1)}

从这个render函数中我们看到on:{"child-event":handleChildEvent}。走到这里还记得我们创建子组件VNode函数的地方吗?

对,createComponent函数,看看这个函数体里面关于对on的处理:

// extract listeners, since these needs to be treated as
    // child component listeners instead of DOM listeners
    var listeners = data.on;

 

var vnode = new VNode(
      ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
      data, undefined, undefined, undefined, context,
      { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
      asyncFactory
    );

把data.on取出来放到了VNode中的componentOptions属性里面。接着往下看,看看对listeners的处理:

  function initInternalComponent (vm, options) {
    var opts = vm.$options = Object.create(vm.constructor.options);
    // doing this because it's faster than dynamic enumeration.
    var parentVnode = options._parentVnode;
    opts.parent = options.parent;
    opts._parentVnode = parentVnode;

    var vnodeComponentOptions = parentVnode.componentOptions;
    opts.propsData = vnodeComponentOptions.propsData;
    opts._parentListeners = vnodeComponentOptions.listeners;
    opts._renderChildren = vnodeComponentOptions.children;
    opts._componentTag = vnodeComponentOptions.tag;

    if (options.render) {
      opts.render = options.render;
      opts.staticRenderFns = options.staticRenderFns;
    }
  }

通过搜索我们找到了 opts._parentListeners = vnodeComponentOptions.listeners;,把它赋值给了_parentListeners。那么还需要澄清一下这个函数initInternalComponent 什么时候调用的呢? 是在实例化子组件的时候调用_init函数里面调用的。

接下来我们看怎么处理_parentListeners的:

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

在这个函数里面找到了,var listeners = vm.$options._parentListeners;。 initEvents这个函数谁调用的,就不说了。接着看

updateComponentListeners 

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


  function updateListeners (
    on,
    oldOn,
    add,
    remove$$1,
    createOnceHandler,
    vm
  ) {
    var name, def$$1, cur, old, event;
    for (name in on) {
      def$$1 = cur = on[name];
      old = oldOn[name];
      event = normalizeEvent(name);
      if (isUndef(cur)) {
        warn(
          "Invalid handler for event \"" + (event.name) + "\": got " + String(cur),
          vm
        );
      } else if (isUndef(old)) {
        if (isUndef(cur.fns)) {
          cur = on[name] = createFnInvoker(cur, vm);
        }
        if (isTrue(event.once)) {
          cur = on[name] = createOnceHandler(event.name, cur, event.capture);
        }
        add(event.name, cur, event.capture, event.passive, event.params);
      } else if (cur !== old) {
        old.fns = cur;
        on[name] = old;
      }
    }
    for (name in oldOn) {
      if (isUndef(on[name])) {
        event = normalizeEvent(name);
        remove$$1(event.name, oldOn[name], event.capture);
      }
    }
  }

我们看到了for (name in on)  这行代码,通过打印可以看到name就是child-event。这行代码def$$1 = cur = on[name];  就把child-event绑定的函数给取出来了。接着看 add(event.name, cur, event.capture, event.passive, event.params);

function add (event, fn) {
    target.$on(event, fn);
  }

终于找到$on了,就是调用Vue.prototype.$on的地方。这一路走下来就把child-event: handleChildEvent 给注册到_events[]数组中了,供$emit函数来回调。

 

 

 

 

你可能感兴趣的:(VUE学习)