这个系列讲到这里,Vue 根本外围的货色曾经剖析完,然而 Vue 之所以弱小,离不开它提供给用户的一些实用功能,开发者能够更偏差于业务逻辑而非基本功能的实现。例如,在日常开发中,咱们将
@click=***
用得飞起,然而咱们是否思考,Vue 如何在前面为咱们的模板做事件相干的解决,并且咱们常常利用组件的自定义事件去实现父子间的通信,那这个事件和和原生 dom 事件又有不同的中央吗,可能实现通信的原理又是什么,带着纳闷,咱们深刻源码开展剖析。
9.1. 模板编译
Vue
在挂载实例前,有相当多的工作是进行模板的编译,将 template
模板进行编译,解析成 AST
树,再转换成 render
函数,而有了 render
函数后才会进入实例挂载过程。对于事件而言,咱们常常应用 v-on
或者 @
在模板上绑定事件。因而对事件的第一步解决,就是在编译阶段对事件指令做收集解决。
从一个简略的用法剖析编译阶段收集的信息:
<div id="app">
<div v-on:click.stop="doThis"> 点击 </div>
<span>{{count}}</span>
</div>
<script>
var vm = new Vue({el: '#app', data() {return { count: 1} }, methods: {doThis() {++this.count} }})
</script>
咱们之前在将模板编译的时候大抵说过编译的流程,模板编译的入口是在 var ast = parse(template.trim(), options);
中,parse
通过拆分模板字符串,将其解析为一个 AST
树,其中对于属性的解决,在 processAttr
中, 因为分支较多,咱们只剖析例子中的流程。
var dirRE = /^v-|^@|^:/;
function processAttrs (el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
for (i = 0, l = list.length; i < l; i++) {name = rawName = list[i].name; // v-on:click
value = list[i].value; // doThis
if (dirRE.test(name)) { // 匹配 v - 或者 @结尾的指令
el.hasBindings = true;
modifiers = parseModifiers(name.replace(dirRE, ''));// parseModifiers('on:click')
if (modifiers) {name = name.replace(modifierRE, '');
}
if (bindRE.test(name)) { // v-bind 分支
// ... 留到 v -bind 指令时剖析
} else if (onRE.test(name)) { // v-on 分支
name = name.replace(onRE, ''); // 拿到真正的事件 click
isDynamic = dynamicArgRE.test(name);// 动静事件绑定
if (isDynamic) {name = name.slice(1, -1);
}
addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic);
} else { // normal directives
// 其余指令相干逻辑
} else {}}
}
processAttrs
的逻辑尽管较多,然而了解起来较为简单,var dirRE = /^v-|^@|^:/;
是匹配事件相干的正则,命中匹配的记功会失去事件指令相干内容,包含事件自身,事件回调以及事件修饰符。最终通过 addHandler
办法,为 AST
树增加事件相干的属性。而 addHandler
还有一个重要性能是对事件修饰符进行非凡解决。
// el 是以后解析的 AST 树
function addHandler (el,name,value,modifiers,important,warn,range,dynamic) {
modifiers = modifiers || emptyObject;
// passive 和 prevent 不能同时应用,能够参照官网文档阐明
if (
warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. '+'Passive handler can\'t prevent default event.',
range
);
}
// 这部分的逻辑会对非凡的修饰符做字符串拼接的解决,以备后续的应用
if (modifiers.right) {if (dynamic) {name = "(" + name + ")==='click'?'contextmenu':(" + name + ")";
} else if (name === 'click') {
name = 'contextmenu';
delete modifiers.right;
}
} else if (modifiers.middle) {if (dynamic) {name = "(" + name + ")==='click'?'mouseup':(" + name + ")";
} else if (name === 'click') {name = 'mouseup';}
}
if (modifiers.capture) {
delete modifiers.capture;
name = prependModifierMarker('!', name, dynamic);
}
if (modifiers.once) {
delete modifiers.once;
name = prependModifierMarker('~', name, dynamic);
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive;
name = prependModifierMarker('&', name, dynamic);
}
// events 用来记录绑定的事件
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {events = el.events || (el.events = {});
}
var newHandler = rangeSetItem({value: value.trim(), dynamic: dynamic }, range);
if (modifiers !== emptyObject) {newHandler.modifiers = modifiers;}
var handlers = events[name];
/* istanbul ignore if */
// 绑定的事件能够多个,回调也能够多个,最终会合并到数组中
if (Array.isArray(handlers)) {important ? handlers.unshift(newHandler) : handlers.push(newHandler);
} else if (handlers) {events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
} else {events[name] = newHandler;
}
el.plain = false;
}
修饰符的解决会扭转最终字符串的拼接后果,咱们看最终转换的 AST
树:
9.2. 代码生成
模板编译的最初一步是依据解析完的 AST
树生成对应平台的渲染函数,也就是 render
函数的生成过程, 对应var code = generate(ast, options);
。
function generate (ast,options) {var state = new CodegenState(options);
var code = ast ? genElement(ast, state) : '_c("div")';
return {render: ("with(this){return" + code + "}"), // with 函数
staticRenderFns: state.staticRenderFns
}
}
其中外围解决在 getElement
中,getElement
函数会依据不同指令类型解决不同的分支,对于一般模板的编译会进入 genData
函数中解决,同样剖析只针对事件相干的解决,从后面解析出的 AST
树显著看出,AST
树中多了 events
的属性,genHandlers
函数会为 event
属性做逻辑解决。
function genData (el, state) {
var data = '{';
// directives first.
// directives may mutate the el's other properties before they are generated.
var dirs = genDirectives(el, state);
if (dirs) {data += dirs + ',';}
// 其余解决
···
// event handlers
if (el.events) {data += (genHandlers(el.events, false)) + ",";
}
···
return data
}
genHandlers
的逻辑,会遍历解析好的 AST
树,拿到 event
对象属性,并依据属性上的事件对象拼接成字符串。参考 Vue3 源码视频解说:进入学习
function genHandlers (events,isNative) {
var prefix = isNative ? 'nativeOn:' : 'on:';
var staticHandlers = "";
var dynamicHandlers = "";
// 遍历 ast 树解析好的 event 对象
for (var name in events) {
//genHandler 实质上是将事件对象转换成可拼接的字符串
var handlerCode = genHandler(events[name]);
if (events[name] && events[name].dynamic) {dynamicHandlers += name + "," + handlerCode + ",";} else {staticHandlers += "\"" + name + "\":"+ handlerCode +",";}
}
staticHandlers = "{" + (staticHandlers.slice(0, -1)) + "}";
if (dynamicHandlers) {return prefix + "_d(" + staticHandlers + ",[" + (dynamicHandlers.slice(0, -1)) + "])"
} else {return prefix + staticHandlers}
}
// 事件模板书写匹配
var isMethodPath = simplePathRE.test(handler.value); // doThis
var isFunctionExpression = fnExpRE.test(handler.value); // () => {} or function() {}
var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')); // doThis($event)
function genHandler (handler) {if (!handler) {return 'function(){}'}
// 事件绑定能够多个,多个在解析 ast 树时会以数组的模式存在,如果有多个则会递归调用 getHandler 办法返回数组。if (Array.isArray(handler)) {return ("[" + (handler.map(function (handler) {return genHandler(handler); }).join(',')) + "]")
}
// value:doThis 能够有三种形式
var isMethodPath = simplePathRE.test(handler.value); // doThis
var isFunctionExpression = fnExpRE.test(handler.value); // () => {} or function() {}
var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')); // doThis($event)
// 没有任何修饰符
if (!handler.modifiers) {
// 合乎函数定义标准,则间接返回调用函数名 doThis
if (isMethodPath || isFunctionExpression) {return handler.value}
// 不合乎则通过 function 函数封装返回
return ("function($event){" + (isFunctionInvocation ? ("return" + (handler.value)) : handler.value) + "}") // inline statement
} else {// 蕴含修饰符的场景}
}
模板中事件的写法有三种, 别离对应上诉上个正则匹配的内容。
-
<div @click="doThis"></div>
-
<div @click="doThis($event)"></div>
-
<div @click="()=>{}"></div> <div @click="function(){}"></div>
上述对事件对象的转换,如果事件不带任何修饰符,并且满足正确的模板写法,则间接返回调用事件名,如果不满足,则有可能是 <div @click="console.log(11)"></div>
的写法,此时会封装到 function($event){}
中。
蕴含修饰符的场景较多,咱们独自列出剖析。以上文中的例子阐明,modifiers: {stop: true}
会拿到 stop
对应须要增加的逻辑脚本'$event.stopPropagation();'
, 并将它增加到函数字符串中返回。
function genHandler() {// ···} else {
var code = '';
var genModifierCode = '';
var keys = [];
// 遍历 modifiers 上记录的修饰符
for (var key in handler.modifiers) {if (modifierCode[key]) {
// 依据修饰符增加对应 js 的代码
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {keys.push(key);
}
// 针对 exact 的解决
} else if (key === 'exact') {var modifiers = (handler.modifiers);
genModifierCode += genGuard(['ctrl', 'shift', 'alt', 'meta']
.filter(function (keyModifier) {return !modifiers[keyModifier]; })
.map(function (keyModifier) {return ("$event." + keyModifier + "Key"); })
.join('||')
);
} else {keys.push(key);
}
}
if (keys.length) {code += genKeyFilter(keys);
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {code += genModifierCode;}
// 依据三种不同的书写模板返回不同的字符串
var handlerCode = isMethodPath
? ("return" + (handler.value) + "($event)")
: isFunctionExpression
? ("return (" + (handler.value) + ")($event)")
: isFunctionInvocation
? ("return" + (handler.value))
: handler.value;
return ("function($event){" + code + handlerCode + "}")
}
}
var modifierCode = {stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard("$event.target !== $event.currentTarget"),
ctrl: genGuard("!$event.ctrlKey"),
shift: genGuard("!$event.shiftKey"),
alt: genGuard("!$event.altKey"),
meta: genGuard("!$event.metaKey"),
left: genGuard("'button' in $event && $event.button !== 0"),
middle: genGuard("'button' in $event && $event.button !== 1"),
right: genGuard("'button' in $event && $event.button !== 2")
};
通过这一转换后,生成 with
封装的 render
函数如下:
"_c('div',{attrs:{"id":"app"}},[_c('div',{on:{"click":function($event){$event.stopPropagation();return doThis($event)}}},[_v(" 点击 ")]),_v(" "),_c('span',[_v(_s(count))])])"
9.3. 事件绑定
后面花了大量的篇幅介绍了模板上的事件标记在构建 AST
树上是怎么解决,并且如何依据构建的 AST
树返回正确的 render
渲染函数,然而真正事件绑定还是离不开绑定注册事件 。这一个阶段就是产生在组件挂载的阶段。
有了 render
函数,天然能够生成实例挂载须要的 Vnode
树,并且会进行 patchVnode
的环节进行实在节点的构建,如果发现过程曾经忘记,能够回顾以往章节。Vnode
树的构建过程和之前介绍的内容没有显著的区别,所以这个过程就不做赘述,最终生成的 vnode
如下:
有了 Vnode
, 接下来会遍历子节点递归调用createElm
为每个子节点创立实在的 DOM
, 因为Vnode
中有 data
属性,在创立实在 DOM
时会进行注册相干钩子的过程,其中一个就是注册事件相干解决。
function createElm() {
···
// 针对指令的解决
if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue);
}
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {if (isDef(i.create)) {i.create(emptyNode, vnode); }
if (isDef(i.insert)) {insertedVnodeQueue.push(vnode); }
}
}
var events = {
create: updateDOMListeners,
update: updateDOMListeners
};
咱们常常会在 template
模板中定义 v-on
事件,v-bind
动静属性,v-text
动静指令等,和 v-on
事件指令一样,他们都会在编译阶段和 Vnode
生成阶段创立 data
属性,因而 invokeCreateHooks
就是一个模板指令解决的工作,他别离针对不同的指令为实在阶段创立不同的工作。针对事件,这里会调用 updateDOMListeners
对实在的 DOM
节点注册事件工作。
function updateDOMListeners (oldVnode, vnode) {
// on 是事件指令的标记
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {return}
// 新旧节点不同的事件绑定解绑
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
// 拿到须要增加事件的实在 DOM 节点
target$1 = vnode.elm;
// normalizeEvents 是对事件兼容性的解决
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, createOnceHandler$1, vnode.context);
target$1 = undefined;
}
其中 normalizeEvents
是针对 v-model
的解决, 例如在 IE 下不反对 change
事件,只能用 input
事件代替。
updateListeners
的逻辑也很简略,它会遍历 on
事件对新节点事件绑定注册事件,对旧节点移除事件监听,它即要解决原生 DOM
事件的增加和移除,也要解决自定义事件的增加和移除,对于自定义事件,后续内容再剖析。
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)) {
// createFunInvoker 返回事件最终执行的回调函数
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);
}
}
}
在初始构建实例时,旧节点是不存在的, 此时会调用 createFnInvoker
函数对事件回调函数做一层封装,因为单个事件的回调能够有多个,因而 createFnInvoker
的作用是对单个,多个回调事件对立封装解决,返回一个当事件触发时真正执行的匿名函数。
function createFnInvoker (fns, vm) {
// 当事件触发时,执行 invoker 办法,办法执行 fns
function invoker () {
var arguments$1 = arguments;
var fns = invoker.fns;
// fns 是多个回调函数组成的数组
if (Array.isArray(fns)) {var cloned = fns.slice();
for (var i = 0; i < cloned.length; i++) {
// 遍历执行真正的回调函数
invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler");
}
} else {
// return handler return value for single handlers
return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler")
}
}
invoker.fns = fns;
// 返回最终事件执行的回调函数
return invoker
}
其中 invokeWithErrorHandling
会执行定义好的回调函数,这里做了同步异步回调的错误处理。try-catch
用于同步回调捕捉异样谬误,Promise.catch
用于捕捉异步工作返回谬误。
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)) {
// issue #9511
// reassign to res to avoid catch triggering multiple times when nested calls
// 当生命周期钩子函数外部执行返回 promise 对象是,如果捕捉异样,则会对异样信息做一层包装返回
res = res.catch(function (e) {return handleError(e, vm, info + "(Promise/async)"); });
}
} catch (e) {handleError(e, vm, info);
}
return res
}
如果事件只触发一次 (即应用了once
修饰符),则调用 createOnceHandler
匿名,在执行完回调之后,移除事件绑定。
function createOnceHandler (event, handler, capture) {
var _target = target$1;
return function onceHandler () {
// 调用事件回调
var res = handler.apply(null, arguments);
if (res !== null) {
// 移除事件绑定
remove$2(event, onceHandler, capture, _target);
}
}
}
add
和 remove
是真正在 DOM
上绑定事件和解绑事件的过程,它的实现也是利用了原生 DOM
的addEventListener,removeEventListener api
。
function add (name,handler,capture,passive){
···
target$1.addEventListener(name,handler,
supportsPassive
? {capture: capture, passive: passive}
: capture);
}
function remove (name,handler,capture,_target) {(_target || target$1).removeEventListener(
name,
handler._wrapper || handler,
capture
);
}
另外事件的解绑除了产生在只触发一次的事件,也产生在组件更新 patchVnode
过程,具体不开展剖析,能够参考之前介绍组件更新的内容钻研 updateListeners
的过程。
9.4. 自定义事件
Vue
如何解决原生的 Dom
事件根本流程曾经讲完,然而针对事件还有一个重要的概念不可疏忽,那就是组件的自定义事件。咱们晓得父子组件能够利用事件进行通信,子组件通过 vm.$emit
向父组件散发事件,父组件通过 v-on:(event)
接管信息并解决回调。因而针对自定义事件在源码中天然有不同的解决逻辑。咱们先通过简略的例子开展。
<script>
var child = {template: `<div @click="emitToParent"> 点击传递信息给父组件 </div>`, methods: { emitToParent() {this.$emit('myevent', 1) } } } new Vue({el: '#app', components: { child}, template: `<div id="app"><child @myevent="myevent" @click.native="nativeClick"></child></div>`, methods: {myevent(num) {console.log(num) }, nativeClick() { console.log('nativeClick') } } }) </script>
从例子中能够看出,一般节点只能应用原生 DOM
事件,而组件上却能够应用自定义的事件和原生的 DOM
事件,并且通过 native
修饰符辨别,有了原生 DOM
对于事件处理的根底,接下来咱们看看自定义事件有什么特别之处。
9.4.1 模板编译
回过头来看看事件的模板编译,在生成 AST
树阶段,之前剖析说过 addHandler
办法会对事件的修饰符做不同的解决,当遇到 native
修饰符时,事件相干属性办法会增加到 nativeEvents
属性中。
下图是 child
生成的 AST
树:
9.4.2 代码生成
不论是组件还是一般标签,事件处理代码都在 genData
的过程中,和之前剖析原生事件统一,genHandlers
用来处理事件对象并拼接成字符串。
function genData() {
···
if (el.events) {data += (genHandlers(el.events, false)) + ",";
}
if (el.nativeEvents) {data += (genHandlers(el.nativeEvents, true)) + ",";
}
}
getHandlers
的逻辑后面曾经讲过,解决组件原生事件和自定义事件的区别在 isNative
选项上,咱们看最终生成的代码为:
with(this){return _c('div',{attrs:{"id":"app"}},[_c('child',{on:{"myevent":myevent},nativeOn:{"click":function($event){return nativeClick($event)}}})],1)}
有了 render
函数接下来会依据它创立 Vnode
实例,其中遇到组件占位符节点时会创立子组件 Vnode
,此时为on,nativeOn
做了一层非凡的转换,将 nativeOn
赋值给 on
, 这样后续的解决形式和一般节点统一。另外,将on
赋值给 listeners
, 在创立VNode
时以组件配置 componentOptions
传入。
// 创立子组件过程
function createComponent (){
···
var listeners = data.on;
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn;
···
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
);
return vnode
}
9.4.3 子组件实例
接下来是通过 Vnode
生成实在节点的过程,这个过程遇到子 Vnode
会实例化子组件实例。实例化子类结构器的过程又回到之前文章剖析的初始化选项配置的过程,在系列最开始的时候剖析 Vue.prototype.init
的过程,跳过了组件初始化的流程,其中针对自定义事件的解决的要害如下
Vue.prototype._init = function(options) {
···
// 针对子组件的事件处理逻辑
if (options && options._isComponent) {
// 初始化外部组件
initInternalComponent(vm, options);
} else {
// 选项合并,将合并后的选项赋值给实例的 $options 属性
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// 初始化事件处理
initEvents(vm);
}
function initInternalComponent (vm, options) {var opts = vm.$options = Object.create(vm.constructor.options);
···
opts._parentListeners = vnodeComponentOptions.listeners;
···
}
此时,子组件拿到了父占位符节点定义的 @myevent="myevent"
事件 。接下来进行子组件的初始化事件处理,此时vm.$options._parentListeners
会拿到父组件自定义的事件。而带有自定义事件的组件会执行 updateComponentListeners
函数。
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);
}
}
之后又回到了之前剖析的 updateListeners
过程,和原生 DOM
事件不同的是,自定义事件的增加移除的办法不同。
var target = vm;
function add (event, fn) {target.$on(event, fn);
}
function remove$1 (event, fn) {target.$off(event, fn);
}
function updateComponentListeners (vm,listeners,oldListeners) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm);
target = undefined;
}
9.4.4 事件 API
咱们回头来看看 Vue
在引入阶段对事件的解决还做了哪些初始化操作。Vue
在实例上用一个 _events
属性存贮治理事件的派发和更新,暴露出 $on, $once, $off, $emit
办法给内部治理事件和派发执行事件。
eventsMixin(Vue); // 定义事件相干函数
function eventsMixin (Vue) {
var hookRE = /^hook:/;
// $on 办法用来监听事件,执行回调
Vue.prototype.$on = function (event, fn) {
var vm = this;
// event 反对数组模式。if (Array.isArray(event)) {for (var i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn);
}
} else {
// _events 数组中记录须要监听的事件以及事件触发的回调
(vm._events[event] || (vm._events[event] = [])).push(fn);
if (hookRE.test(event)) {vm._hasHookEvent = true;}
}
return vm
};
// $once 办法用来监听一次事件,执行回调
Vue.prototype.$once = function (event, fn) {
var vm = this;
// 对 fn 做一层包装,先解除绑定再执行 fn 回调
function on () {vm.$off(event, on);
fn.apply(vm, arguments);
}
on.fn = fn;
vm.$on(event, on);
return vm
};
// $off 办法用来解除事件监听
Vue.prototype.$off = function (event, fn) {
var vm = this;
// 如果 $off 办法没有传递任何参数时,将_events 属性清空。if (!arguments.length) {vm._events = Object.create(null);
return vm
}
// 数组解决
if (Array.isArray(event)) {for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {vm.$off(event[i$1], fn);
}
return vm
}
var cbs = vm._events[event];
if (!cbs) {return vm}
if (!fn) {vm._events[event] = null;
return vm
}
// specific handler
var cb;
var i = cbs.length;
while (i--) {cb = cbs[i];
if (cb === fn || cb.fn === fn) {
// 将监听的事件回调移除
cbs.splice(i, 1);
break
}
}
return vm
};
// $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
};
}
有了这些事件 api,自定义事件的增加移除了解起来也简略很多。组件通过 this.$emit
在组件实例中派发了事件,而在这之前,组件曾经将须要监听的事件以及回调增加到实例的 _events
属性中,触发事件时便能够间接执行监听事件的回调。
最初,咱们换一个角度了解父子组件通信,组件自定义事件的触发和监听实质上都是在以后的组件实例中进行,之所以能产生父子组件通信的成果是因为事件监听的回调函数写在了父组件中。
9.5 小结
事件是咱们日常开发中必不可少的性能点,Vue
在应用层裸露了 @,v-on
的指令供开发者在模板中绑定事件。事件指令在模板编译阶段会以属性的模式存在,而在实在节点渲染阶段会依据事件属性去绑定相干的事件。对于组件的事件而言,咱们能够利用事件进行子父组件间的通信,他实质上是在同个子组件外部保护了一个事件总线,从剖析后果能够看出,之所以有子父组件通信的成果,起因仅仅是因为回调函数写在了父组件中。