发布网友 发布时间:2024-09-05 05:37
共1个回答
热心网友 时间:2024-09-29 17:39
之前我们已经介绍了event的编译过程(点击这里跳转),接下来我们分析在Vue初始化和更新的过程中event的内部是如何生成的。
event生成之自定义事件Vue中event事件分为原生DOM事件与自定义事件,原生DOM事件的处理(点击这里跳转),我们上一节已经分析过了。这一节我们来分析下自定义事件。
自定义事件是用在组件节点上的,组件节点上定义的事件可以分为两类:一类是原生DOM事件(在vue2.x版本在组件节点上使用原生DOM事件需要添加native修饰符),另一类就是自定义事件。
下面我们来分析自定义事件的流程:
创建组件vnode创建组建vnode(虚拟节点)的时候会执行createComponent函数,其中有如下逻辑:
exportfunctioncreateComponent(Ctor:Class<Component>|Function|Object|void,data:?VNodeData,context:Component,children:?Array<VNode>,tag?:string):VNode|Array<VNode>|void{......//extractlisteners,sincetheseneedstobetreatedas//childcomponentlistenersinsteadofDOMlisteners//自定义事件赋值给listenersconstlisteners=data.on//replacewithlistenerswith.nativemodifier//soitgetsprocessedduringparentcomponentpatch.//native事件赋值给data.on,这样原生方法直接就上一节相同的逻辑了data.on=data.nativeOn......//returnaplaceholdervnode//创建占位符vnodeconstname=Ctor.options.name||tag//生成虚拟节点的时候,将listeners当参数传入constvnode=newVNode(`vue-component-${Ctor.cid}${name?`-${name}`:''}`,data,undefined,undefined,undefined,context,{Ctor,propsData,listeners,tag,children},asyncFactory)//返回vnodereturnvnode}创建组件vnode的过程中会将组件节点上的定义的自定义事件赋值给listeners变量,同时将组件节点上定义的原生事件赋值给data.on属性,这样,组件的原生事件就会执行如同上一节生成原生事件相同的逻辑。然后在创建组件vnode的时候,会将listeners(缓存了自定义事件)当做第七个参数(componentOptions)的属性值。
vnode创建完成之后,在初始化组件的时候,会执行initInternalComponent函数:
组件初始化initInternalComponentexportfunctioninitInternalComponent(vm:Component,options:InternalComponentOptions){//子组件构造器的options(配置项)constopts=vm.$options=Object.create(vm.constructor.options)//....//我们之前创建的节点的第七个参数(componentOptions)constvnodeComponentOptions=parentVnode.componentOptions//子组件构造器的_parentListeners属性指向之前定义的listeners(组件自定义事件)opts._parentListeners=vnodeComponentOptions.listeners//...}执行完这些配置项的生成之后,会初始化子组件事件
exportfunctioninitEvents(vm:Component){vm._events=Object.create(null)vm._hasHookEvent=false//initparentattachedeventsconstlisteners=vm.$options._parentListeners//有listeners,执行updateComponentListenersif(listeners){updateComponentListeners(vm,listeners)}}listeners非空,执行updateComponentListeners函数:
lettarget:anyexportfunctionupdateComponentListeners(vm:Component,listeners:Object,oldListeners:?Object){//target指向当前实例target=vm//执行updateListenersupdateListeners(listeners,oldListeners||{},add,remove,vm)target=undefined}这个地方同样执行updateListeners函数,与上一节原生DOM事件的生成相同,但与原生DOM事件的生成有几处不同之处,如下add与remove函数的定义。
functionadd(event,fn,once){if(once){//如果有once属性,执行$once方法target.$once(event,fn)}else{否则执行$on方法target.$on(event,fn)}}functionremove(event,fn){//remove方法是执行$off方法target.$off(event,fn)}关于$once、$on、$off函数都定义在eventsMixin中:
exportfunctioneventsMixin(Vue:Class<Component>){consthookRE=/^hook:/Vue.prototype.$on=function(event:string|Array<string>,fn:Function):Component{......}Vue.prototype.$once=function(event:string,fn:Function):Component{......}Vue.prototype.$off=function(event?:string|Array<string>,fn?:Function):Component{......}Vue.prototype.$emit=function(event:string):Component{......}}$onVue.prototype.$on=function(event:string|Array<string>,fn:Function):Component{//当前实例就是调用该方法的实例constvm:Component=this//如果event是数组,遍历数组,依次执行$on函数if(Array.isArray(event)){for(leti=0,l=event.length;i<l;i++){this.$on(event[i],fn)}}else{//将当前实例的_events属性初始化为空数组并push当前添加的函数(vm._events[event]||(vm._events[event]=[])).push(fn)//optimizehook:eventcostbyusingabooleanflagmarkedatregistration//insteadofahashlookupif(hookRE.test(event)){vm._hasHookEvent=true}}returnvm}$on的逻辑就是将当前的方法存入当前实例vm._events属性中。
$onceVue.prototype.$once=function(event:string,fn:Function):Component{//当前实例就是调用该方法的实例constvm:Component=this//定义on函数functionon(){//执行$off销毁当前事件vm.$off(event,on)//执行函数fnfn.apply(vm,arguments)}//on的fn属性指向当前传入的函数on.fn=fn//将on函数存入vm._events中vm.$on(event,on)returnvm}$once的逻辑就是对传入的fn函数做了一层封装,生成了一个内部函数on,on.fn属性指向传入函数fn,将on函数存入实例的_events属性对象中,这样执行完一次这个函数后,该函数就被销毁了。
$offVue.prototype.$off=function(event?:string|Array<string>,fn?:Function):Component{//当前实例就是调用该方法的实例constvm:Component=this//all//如果没有传参数,将vm._events置为空对象if(!arguments.length){vm._events=Object.create(null)returnvm}//arrayofevents//event如果是数组,遍历该数组,依次调用$off函数if(Array.isArray(event)){for(leti=0,l=event.length;i<l;i++){this.$off(event[i],fn)}//返回returnvm}//specificevent//唯一的eventconstcbs=vm._events[event]//cbs未定义,直接返回if(!cbs){returnvm}//fn未定义(未传入fn的情况下),vm._events[event]赋值为空,直接返回if(!fn){vm._events[event]=nullreturnvm}//fn定义了if(fn){//specifichandlerletcbleti=cbs.length//遍历cbs对象while(i--){cb=cbs[i]//如果查找到有属性与fn相同if(cb===fn||cb.fn===fn){//移除该属性,跳出循环cbs.splice(i,1)break}}}returnvm}$off的作用就是移除vm._events对象上定义的事件函数。
eventsMixin中还定义了一个函数$emit,在组件通讯的时候经常使用:
$emitVue.prototype.$emit=function(event:string):Component{//当前实例就是调用该方法的实例constvm:Component=thisif(process.env.NODE_ENV!=='production'){constlowerCaseEvent=event.toLowerCase()if(lowerCaseEvent!==event&&vm._events[lowerCaseEvent]){tip(`Event"${lowerCaseEvent}"isemittedincomponent`+`${formatComponentName(vm)}butthehandlerisregisteredfor"${event}".`+`NotethatHTMLattributesarecase-insensitiveandyoucannotuse`+`v-ontolistentocamelCaseeventswhenusingin-DOMtemplates.`+`Youshouldprobablyuse"${hyphenate(event)}"insteadof"${event}".`)}}//拿到vm._events的event事件上的所有函数letcbs=vm._events[event]//存在cbsif(cbs){//cbs转化cbs=cbs.length>1?toArray(cbs):cbs//其他参数转化成数组constargs=toArray(arguments,1)//遍历cbs,依次执行其中的函数for(leti=0,l=cbs.length;i<l;i++){try{cbs[i].apply(vm,args)}catch(e){handleError(e,vm,`eventhandlerfor"${event}"`)}}}returnvm}从源码上可以看出,在我们平时开发过程中,其实看似通过$emit方法调用父组件上的函数,本质上是调用组件自身实例上定义的函数,而这个函数是在组件生成的过程中传入到子组件的配置项中的。
还有一点值得提一下,组件自定义事件的事件调用,其实就是非常经典的事件中心的实现。而我们在Vue开发过程中常用的eventBus的实现,原理也是同上。
到此为止,关于Vue的event原理已经大致介绍完毕了,欢迎交流探讨。
原文:https://juejin.cn/post/7097405336251793438