还是从一个简单例子看下这个过程:
例子也非常简单,子组件通过$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函数来回调。