网站建设公司哪个好呀net网站建设,上海做网站公司品划网络,小蝌蚪紧急自动跳转中,做课宝官方网站文章目录 1. 前言2. 解析事件3. initEvents函数分析4. 总结 1. 前言
本篇文章介绍生命周期初始化阶段所调用的第二个初始化函数——initEvents。从函数名字上来看#xff0c;这个初始化函数是初始化实例的事件系统。我们知道#xff0c;在Vue中#xff0c;当我们在父组件中… 文章目录 1. 前言2. 解析事件3. initEvents函数分析4. 总结 1. 前言
本篇文章介绍生命周期初始化阶段所调用的第二个初始化函数——initEvents。从函数名字上来看这个初始化函数是初始化实例的事件系统。我们知道在Vue中当我们在父组件中使用子组件时可以给子组件上注册一些事件这些事件即包括使用v-on或注册的自定义事件也包括注册的浏览器原生事件需要加 .native 修饰符如下
child selectselectHandler click.nativeclickHandler/child不管是什么事件当子组件即实例在初始化的时候都需要进行一定的初始化那么本篇文章就来看看实例上的事件都是如何进行初始化的。
2. 解析事件
我们先从解析事件开始说起回顾之前的模板编译解析中当遇到开始标签的时候除了会解析开始标签还会调用processAttrs 方法解析标签中的属性processAttrs 方法位于源码的 src/compiler/parser/index.js中 如下
export const onRE /^|^v-on:/
export const dirRE /^v-|^|^:/function processAttrs (el) {const list el.attrsListlet i, l, name, value, modifiersfor (i 0, l list.length; i l; i) {name list[i].namevalue list[i].valueif (dirRE.test(name)) {// 解析修饰符modifiers parseModifiers(name)if (modifiers) {name name.replace(modifierRE, )}if (onRE.test(name)) { // v-onname name.replace(onRE, )addHandler(el, name, value, modifiers, false, warn)}}}
}从上述代码中可以看到在对标签属性进行解析时判断如果属性是指令首先通过 parseModifiers 解析出属性的修饰符然后判断如果是事件的指令则执行 addHandler(el, name, value, modifiers, false, warn) 方法 该方法定义在 src/compiler/helpers.js 中如下
export function addHandler (el,name,value,modifiers) {modifiers modifiers || emptyObject// check capture modifier 判断是否有capture修饰符if (modifiers.capture) {delete modifiers.capturename ! name // 给事件名前加!用以标记capture修饰符}// 判断是否有once修饰符if (modifiers.once) {delete modifiers.oncename ~ name // 给事件名前加~用以标记once修饰符}// 判断是否有passive修饰符if (modifiers.passive) {delete modifiers.passivename name // 给事件名前加用以标记passive修饰符}let eventsif (modifiers.native) {delete modifiers.nativeevents el.nativeEvents || (el.nativeEvents {})} else {events el.events || (el.events {})}const newHandler: any {value: value.trim()}if (modifiers ! emptyObject) {newHandler.modifiers modifiers}const handlers events[name]if (Array.isArray(handlers)) {handlers.push(newHandler)} else if (handlers) {events[name] [handlers, newHandler]} else {events[name] newHandler}el.plain false
}在addHandler 函数里做了 3 件事情首先根据 modifier 修饰符对事件名 name 做处理接着根据 modifier.native 判断事件是一个浏览器原生事件还是自定义事件分别对应 el.nativeEvents 和 el.events最后按照 name 对事件做归类并把回调函数的字符串保留到对应的事件中。
在前言中的例子中父组件的 child 节点生成的 el.events 和 el.nativeEvents 如下
el.events {select: {value: selectHandler}
}el.nativeEvents {click: {value: clickHandler}
}然后在模板编译的代码生成阶段会在 genData 函数中根据 AST 元素节点上的 events 和 nativeEvents 生成_c(tagName,data,children)函数中所需要的 data 数据它的定义在 src/compiler/codegen/index.js 中
export function genData (el state) {let data {// ...if (el.events) {data ${genHandlers(el.events, false,state.warn)},}if (el.nativeEvents) {data ${genHandlers(el.nativeEvents, true, state.warn)},}// ...return data
}生成的data数据如下
{// ...on: {select: selectHandler},nativeOn: {click: function($event) {return clickHandler($event)}}// ...
}可以看到最开始的模板中标签上注册的事件最终会被解析成用于创建元素型VNode的_c(tagName,data,children)函数中data数据中的两个对象自定义事件对象on浏览器原生事件nativeOn。
在前面的文章中我们说过模板编译的最终目的是创建render函数供挂载的时候调用生成虚拟DOM那么在挂载阶段 如果被挂载的节点是一个组件节点则通过 createComponent 函数创建一个组件 vnode该函数位于源码的 src/core/vdom/create-component.js 中 如下
export function createComponent (Ctor: ClassComponent | Function | Object | void,data: ?VNodeData,context: Component,children: ?ArrayVNode,tag?: string
): VNode | ArrayVNode | void {// ...const listeners data.ondata.on data.nativeOn// ...const name Ctor.options.name || tagconst vnode new VNode(vue-component-${Ctor.cid}${name ? -${name} : },data, undefined, undefined, undefined, context,{ Ctor, propsData, listeners, tag, children },asyncFactory)return vnode
}可以看到把 自定义事件data.on 赋值给了 listeners把浏览器原生事件 data.nativeOn 赋值给了 data.on这说明所有的原生浏览器事件处理是在当前父组件环境中处理的。而对于自定义事件会把 listeners 作为 vnode 的 componentOptions 传入放在子组件初始化阶段中处理 在子组件的初始化的时候 拿到了父组件传入的 listeners然后在执行 initEvents 的过程中会处理这个 listeners。
所以铺垫了这么多结论来了父组件给子组件的注册事件中把自定义事件传给子组件在子组件实例化的时候进行初始化而浏览器原生事件是在父组件中处理。
换句话说实例初始化阶段调用的初始化事件函数initEvents实际上初始化的是父组件在模板中使用v-on或注册的监听子组件内触发的事件。
3. initEvents函数分析
了解了以上过程之后我们终于进入了正题开始分析initEvents函数该函数位于源码的src/instance/events.js中如下
export function initEvents (vm: Component) {vm._events Object.create(null)// init parent attached eventsconst listeners vm.$options._parentListenersif (listeners) {updateComponentListeners(vm, listeners)}
}可以看到initEvents函数逻辑非常简单首先在vm上新增_events属性并将其赋值为空对象用来存储事件。
vm._events Object.create(null)接着获取父组件注册的事件赋给listeners如果listeners不为空则调用updateComponentListeners函数将父组件向子组件注册的事件注册到子组件的实例中如下
const listeners vm.$options._parentListeners
if (listeners) {updateComponentListeners(vm, listeners)
}这个updateComponentListeners函数是什么呢该函数定义如下
export function updateComponentListeners (vm: Component,listeners: Object,oldListeners: ?Object
) {target vmupdateListeners(listeners, oldListeners || {}, add, remove, vm)target undefined
}function add (event, fn, once) {if (once) {target.$once(event, fn)} else {target.$on(event, fn)}
}function remove (event, fn) {target.$off(event, fn)
}可以看到updateComponentListeners函数其实也没有干什么只是调用了updateListeners函数并把listeners以及add和remove这两个函数传入。我们继续跟进看看updateListeners函数干了些什么updateListeners函数位于源码的src/vdom/helpers/update-listeners.js中如下
export function updateListeners (on: Object,oldOn: Object,add: Function,remove: Function,vm: Component
) {let name, def, cur, old, eventfor (name in on) {def cur on[name]old oldOn[name]event normalizeEvent(name)if (isUndef(cur)) {process.env.NODE_ENV ! production warn(Invalid handler for event ${event.name}: got String(cur),vm)} else if (isUndef(old)) {if (isUndef(cur.fns)) {cur on[name] createFnInvoker(cur)}add(event.name, cur, event.once, event.capture, event.passive, event.params)} else if (cur ! old) {old.fns curon[name] old}}for (name in oldOn) {if (isUndef(on[name])) {event normalizeEvent(name)remove(event.name, oldOn[name], event.capture)}}
}可以看到该函数的作用是对比listeners和oldListeners的不同并调用参数中提供的add和remove进行相应的注册事件和卸载事件。其思想是如果listeners对象中存在某个key即事件名而oldListeners中不存在则说明这个事件是需要新增的反之如果oldListeners对象中存在某个key即事件名而listeners中不存在则说明这个事件是需要从事件系统中卸载的
该函数接收5个参数分别是on、oldOn、add、remove、vm其中on对应listenersoldOn对应oldListeners。
首先对on进行遍历 获得每一个事件名然后调用 normalizeEvent 函数关于该函数下面会介绍处理 处理完事件名后 判断事件名对应的值是否存在如果不存在则抛出警告如下
for (name in on) {def cur on[name]old oldOn[name]event normalizeEvent(name)if (isUndef(cur)) {process.env.NODE_ENV ! production warn(Invalid handler for event ${event.name}: got String(cur),vm)}
}如果存在则继续判断该事件名在oldOn中是否存在如果不存在则调用add注册事件如下
if (isUndef(old)) {if (isUndef(cur.fns)) {cur on[name] createFnInvoker(cur)}add(event.name, cur, event.once, event.capture, event.passive, event.params)
}这里定义了 createFnInvoker 方法并返回invoker函数:
export function createFnInvoker (fns) {function invoker () {const fns invoker.fnsif (Array.isArray(fns)) {const cloned fns.slice()for (let i 0; i cloned.length; i) {cloned[i].apply(null, arguments)}} else {// return handler return value for single handlersreturn fns.apply(null, arguments)}}invoker.fns fnsreturn invoker
}由于一个事件可能会对应多个回调函数所以这里做了数组的判断多个回调函数就依次调用。注意最后的赋值逻辑 invoker.fns fns每一次执行 invoker 函数都是从 invoker.fns 里取执行的回调函数回到 updateListeners当我们第二次执行该函数的时候判断如果 cur ! old那么只需要更改 old.fns cur 把之前绑定的 involer.fns 赋值为新的回调函数即可并且 通过 on[name] old 保留引用关系这样就保证了事件回调只添加一次之后仅仅去修改它的回调函数的引用。
if (cur ! old) {old.fns curon[name] old
}最后遍历 oldOn 获得每一个事件名判断如果事件名在on中不存在则表示该事件是需要从事件系统中卸载的事件则调用 remove方法卸载该事件。
以上就是updateListeners函数的所有逻辑那么上面还遗留了一个normalizeEvent 函数是干什么用的呢还记得我们在解析事件的时候当事件上有修饰符的时候我们会根据不同的修饰符给事件名前面添加不同的符号以作标识其实这个normalizeEvent 函数就是个反向操作根据事件名前面的不同标识反向解析出该事件所带的何种修饰符其代码如下
const normalizeEvent cached((name: string): {name: string,once: boolean,capture: boolean,passive: boolean,handler?: Function,params?: Arrayany
} {const passive name.charAt(0) name passive ? name.slice(1) : nameconst once name.charAt(0) ~name once ? name.slice(1) : nameconst capture name.charAt(0) !name capture ? name.slice(1) : namereturn {name,once,capture,passive}
})可以看到就是判断事件名的第一个字符是何种标识进而判断出事件带有何种修饰符最终将真实事件名及所带的修饰符返回。
4. 总结
本篇文章介绍了生命周期初始化阶段所调用的第二个初始化函数——initEvents。该函数是用来初始化实例的事件系统的。
我们先从模板编译时对组件标签上的事件解析入手分析我们知道了父组件既可以给子组件上绑定自定义事件也可以绑定浏览器原生事件。这两种事件有着不同的处理时机浏览器原生事件是由父组件处理而自定义事件是在子组件初始化的时候由父组件传给子组件再由子组件注册到实例的事件系统中。
也就是说初始化事件函数initEvents实际上初始化的是父组件在模板中使用v-on或注册的监听子组件内触发的事件。
最后分析了initEvents函数的具体实现过程该函数内部首先在实例上新增了_events属性并将其赋值为空对象用来存储事件。接着通过调用updateComponentListeners函数将父组件向子组件注册的事件注册到子组件实例中的_events对象里。