vue事件简介
vue事件有两种:dom事件和自定义事件,前者使用在dom元素上,可以使用v-on或@来监听事件,自定义事件用于组件上,方便组件间的通信,如果在组件上使用原生事件,需要加 .native 修饰符,下面我们从源码分析下vue事件运行的原理
事件的编译
//在节点编译过程中会执行processattrs
function processAttrs (el) {
const list = el.attrsList
let i, l, name, rawName, value, modifiers, syncGen, isDynamic
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name
value = list[i].value
if (dirRE.test(name)) {
// mark element as dynamic
el.hasBindings = true
// modifiers
//parseModifiers方法会对有修饰符的属性,进行处理,得到modifier
modifiers = parseModifiers(name.replace(dirRE, ''))
// support .foo shorthand syntax for the .prop modifier
//解析事件指令,对name中'v-on'字段检测,满足的话把这个字段也去掉,name就变成click,调用addHandler。
//addHandler,给AST节点添加event属性,并根据modifer解析出来的标记给name上打标记
if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
(modifiers || (modifiers = {})).prop = true
name = `.` + name.slice(1).replace(modifierRE, '')
//如果有modifer,把name中modifer相关字段去掉
} else if (modifiers) {
name = name.replace(modifierRE, '')
}
...
addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
...
}
}
addHandler 根据modifer.native判断原生事件还是普通事件。接下来构造event对象,如果有 .native ,构造nativeEvents对象,否则构造events对象。按照name对事件做归类,并把回调函数的字符串保留到对应的事件中
function addHandler (
el,
name,
value,
modifiers,
important,
warn,
range,
dynamic
) {
...
//根据modifer.native判断原生事件还是普通事件,主要是生成events或nativeEvents属性
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
...
el.plain = false;
}
genData函数中根据AST元素节点上的events和nativeEvents生成data数据,genHandlers 根据isNative判断res的值为'nativeOn'/ 'on'。遍历events对象,拼接genHandler会生成handler的代码,拼接出一个JSON代码对象。
{
on: {"select": selectHandler},
nativeOn: {"click": function($event) {
$event.preventDefault();
return clickHandler($event)
}
}
}
事件的运行
dom事件
dom事件的添加和移除,实际上就是调用原生 addEventListener 和 removeEventListener
function add (
name: string,
handler: Function,
capture: boolean,
passive: boolean
) {
...
target.addEventListener(
name,
handler,
supportsPassive
? { capture, passive }
: capture
)
}
function remove (
name: string,
handler: Function,
capture: boolean,
_target?: HTMLElement
) {
(_target || target).removeEventListener(
name,
handler._wrapper || handler,
capture
)
}
自定义事件
对于自定义事件和原生 DOM 事件处理的差异就在事件添加和删除的实现上,自定义事件 add 和 remove 的实现
var target;
//添加事件是在里面调用来$on方法来实现
function add (event, fn) {
target.$on(event, fn);
}
//移除事件是在里面调用来$off方法来实现
function remove$1 (event, fn) {
target.$off(event, fn);
}
自组件触发自定义事件时,回掉函数是在父组件内执行的。
//createComponent。在创建组件vnode之前,会对事件做处理,
//会把data.on(自定义事件)赋值给listeners,listeners在组件实例化成VNode时会作为参数传入
//拿到 listeners 后,执行 updateComponentListeners(vm, listeners) 方法:
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
...
//它把 data.on 赋值给了 listeners,把 data.nativeOn赋值给了 data.on,这样在组件上也可以定义原生事件,
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
...
return vnode
}
Vue为我们提供了四个自定义事件API,分别是$on
,$once
,$off
,$emit
on事件
function eventsMixin (Vue) {
var hookRE = /^hook:/;
//$on方法用来在vm实例上监听一个自定义事件,该事件可用$emit触发。
//当执行 vm.$on(event,fn) 的时候,按事件的名称 event
//把回调函数 fn 存储起来 vm._events[event].push(fn)。
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
};
once事件
//vm.$once执行的时候,内部就是执行 vm.$on
//并且当回调函数执行一次后再通过 vm.$off 移除事件的回调,这样就确保了回调函数只执行一次
Vue.prototype.$once = function (event, fn) {
var vm = this;
function on () {
vm.$off(event, on);
fn.apply(vm, arguments);
}
on.fn = fn;
vm.$on(event, on);
return vm
};
off事件
//当执行 vm.$off(event,fn) 的时候会移除指定事件名 event 和指定的 fn ,根据传入的参数移除相应的监听事件,
Vue.prototype.$off = function (event, fn) {
var vm = this;
// all
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
// array of events
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
}
...
emit事件
//执行 vm.$emit的时候,根据事件名 event 找到所有的回调函数
//let cbs = vm._events[event],然后遍历执行所有的回调函数,根据相应的事件名
//执行不同的回掉函数
Vue.prototype.$emit = function (event) {
var vm = this;
···
return vm
};
}