(function anonymous(
) {
with(this){
return _c('div',[
_c('child',{
on:{"select":selectHandler},
nativeOn:{
"click":function($event){
$event.preventDefault();return clickHandler($event)
}
}
})],1)}
})
- parseHTML 解析 --> events的结果 attrsMap: { @click.native.prevent: "clickHandler",@select: "selectHandler" }
- 在解析Child标签的时候 --> processElement --> processAttrs -->
- 第一个循环(name = @select )来到 else if (onRE.test(name)) 逻辑 -->
- 调用addHandler -->
- modifiers 修饰符不存在 , 事件类型不为click -->
- events = el.events = {} --> ( 为el添加events对象,并获得其内存指针储存在变量中方便后续操作 )
- 定义newHandler = { value: value.trim() }
- 定义 handlers变量 拿到 events[name] --> ( 因为是添加事件,而且同名事件可能是多个所以要先拿到再处理) -->
- 分情况处理
- 情况1 是否为数组
- 情况2 是否已经存在
- 情况3 向前添加事件还是向后添加事件(important)
- plain = false
- 第二个循环( @click.native.prevent ) -->
- parseModifiers(name) --> 可以得到 2个值 --> modifiers: { native: true, prevent: true}
- 调用addHandler (第二次) -->
- 此次进来modifiers有2个值
- 事件类型为click --> 对鼠标事件进行处理( right -> 改为contextmenu , middle -> 改为mouseup ) --> 但不存在这两个修饰符所以name不用修改
- capture(使用事件捕获模式) / once(一次事件) / passive(提升触屏事件触发事件) --> 不存在该三个修饰符不用修改 name -->
- 往下走 modifiers.native 存在 --> 删除nativeshux 并且 events = el.nativeEvents (所以后续对event的操作实际是对el.nativeEvents操作) -->
- 同样的 定义newHandler = { value: value.trim() } -->
- modifiers 存在 --> newHandler 添加新属性--> newHandler.modifiers = modifiers
- 和上述一样分情况处理,修改plain的值
**最后在child标签的ast上有2个表事件的属性 : { events: select: {value: "selectHandler"} , click: modifiers: {prevent: true} , value: "clickHandler" } } **
这就是eventsAST生成的大致流程. 不是很全但只要熟悉就可以很好的理解源码并运用到项目中,最好也可以用在实际架构开发中.
该组件仅有一个div和child 一共2个标签,很显然div无插值表达式和指令 属于静态节点,但是child是一个组件并且有event绑定故为静态节点和静态根,所以div不为静态根
- 来到genChildren (解析child组件) --> genElement --> genData -->
- if ( el.events ) data += (genHandlers(el.events, false)) + ","; --> 解析 select: {value: "selectHandler"} --> genHandlers -->
- genHandlers --> 很显然当前解析的事件类型为events 使用 on:{ 开头 ,同时进入for循环来调用genHandler来对事件进行进一步处理 --> genHandler ( 只有一个select就直接来了 ) -->
- 如果传入的handler为数组那么就 组装数组,循环调用genHandler进行解析
- modifiers 不存在 且 simplePathRE.test(handler.value); == true --> 直接return handler.value -->
- 结果 on:{"select":selectHandler}
- if (el.nativeEvents) data += (genHandlers(el.nativeEvents, true)) + ","; --> 解析 click事件 --> genHandlers -->
- 此时传入的isNative == true --> 使用 nativeOn: { 开头 , 循环解析events -->
- 同样的解析isMethodPath = simplePathRE.test(handler.value); = true ,但此时modifiers有值走else逻辑 -->
- 定义变量 ,for in 枚举modifiers对modifiers进行解析 --> 解析modifiers分3种情况
- modifierCode 中有对应key的值 --> 连接modifierCode中对应的字符串 ,向keys中push key
- key === 'exact' --> 对4个特殊键的处理, 主要用于组合按键使用的事件的处理
- else 逻辑 直接想keys中 push key
- 显然prevent 是存在的 连接modifierCode字符串 --> 枚举结束 -->
- 根据上面isMethodPath = true 组装函数体字符串并返回结果 --> ("function($event){" + code + handlerCode + "}") -->
- 最后得到 --> "nativeOn:{"click":function($event){$event.preventDefault();return clickHandler($event)}}"
- generate结束
(function anonymous(
) {
with(this){
return _c('button',{
on:{
"click":function($event){clickHandler($event)}}
},[_v("click me>")])}
})
- 有了上面的理解,理解child组件的编译就简单很多了
- parseHTML 生成--> attrsMap: {@click: "clickHandler($event)"}
- addHandler -->
- 同样name == click 但不满足 modifiers.right 和 modifiers.middle
- 也不满足三个特殊的修饰符(capture,once,passive)
- 需要注意的是 该事件为 event是而不是 nativeEvents 这个需要注意 --> 虽然事件为原生事件但是没有native修饰符 --> 在后续运行时候也注意一下理解 --> (区别) -->最后
- 生成 --> events: {click: {value: "clickHandler($event)"}}
值得注意的就是此次的click直接和上面父组件的native上的click事件对比,其运行和绑定
同样的很容易得出 Object.keys(node).every(isStaticKey) --> events 不为静态key --> 元素的 static = false 和 staticRoot = false
- generate --> genElement --> 最后的else逻辑 --> genData -->
- 同样的el.events存在 调用 genHandlers( el.events , false ) 进行字符串的拼接 -->
- isNative = false, 使用 on:{ 拼接
- genHandler处理click -->
- handler.value = Object.keys(node).every(isStaticKey) ,不满足 simplePathRE,和 fnExpRE 的正则 而且modifiers不存在 -->
- ("function($event){" + (handler.value) + "}") --> (在外面包装一层返回结果)
- 这就是为什么我们可以获取事件处理event的原因
- 最后得到的结果 --> on:{"click":function($event){clickHandler($event)}}
generate结束 --> 注意查看addHandler ,那就理解为什么,可以访问原生event事件的原因
同样的还是挑重点和逻辑来讲.
开头忽略\跳过部分:
$mount --> mountComonent --> Watcher --> watcher.get() --> updateComponent( --> _createElement() [VNodeCreate] (以及createComponent进行生成组件构造函数)--> _render() \ _ update) --> patch --> createElm --> createChildren --> 渲染子组件 --> 组件是child --> child的createElm
特别说明: 在child组件的createComponent的时候有一段需要注意的代码
// 拿到了on事件,再把原生事件赋值给on
// 而本来的on的事件作为参数传入Vnode的实例
// 使VNode的componentOptions 多了listeners的选项是原始的on
// 而nativeOn 则 --> on
const listeners = data.on
data.on = data.nativeOn
...
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
- 因为child是组件所以走进了createComponent里面
-
i.data存在而且i.data.hook.init也存在 --> 调用组件init的钩子函数 -->
-
在钩子函数里因为是第一次调用所以else逻辑调用createComponentInstanceForVnode --> createComponent --> 调用组件的构造函数 --> 组件构造函数执行vue._init -->
-
组件进行mergeOpts -->
// 有这2句代码 这样从父节点方法就可以通过此应用的关系映射到子节点的$options._parentListeners中 // 同时正因为这个引用形成了子父通信. var vnodeComponentOptions = parentVnode.componentOptions; opts._parentListeners = vnodeComponentOptions.listeners;
-
initLifecycle --> 获取$options.parent 然后把当前 vm push 进 $parent.$children中 --> 形成了父子依赖关系
-
根据上述mergeOptions可得 存在_parentListeners --> initEvents 中调用updateComponentListeners() --> updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm);
-
由上面传入的参数可得 用同一套逻辑对事件进行增删改,同时增删改也是自己定义的
-
枚举传入的事件(on) -->
-
normalizeEvent 对 事件进行处理(3个特殊的事件passive、once、capture)
-
拿到新的绑定的on (cur) 和旧的绑定的 old(old)
-
cur不存在报错、old不存在添加新的、都存在判断是否相等,不相等更新
-
显然old不存在
if (isUndef(cur.fns)) { // 组装invoker函数并把真实调用的函数赋值到invoker.fns中 cur = on[name] = createFnInvoker(cur); } if (isTrue(event.once)) { // 如果normalizeEvent 解析出来是有once 那就调用 createOnceHandler再封装一层 cur = on[name] = createOnceHandler(event.name, cur, event.capture); } // 真正添加方法 add(event.name, cur, event.capture, event.passive, event.params);
-
调用add、显然是调用vm.$on() -->
-
vm指向child , $on的实际操作是对vm._event数组的添加(同理$off是对vm.event的删除)
-
-
枚举完成 为vm._event添加了select数组里面有一个方法
-
枚举old --> 移除不存在或者删除的对应的方法 ($off方法看源码有注释流程就跳过了)
-
-
返回init 跳回component.init钩子的实现 --> 执行$mount方法
-
-
$mount方法 开始一直跳到 createElm(创建buttonElm) --> 创建完children接着往下走 --> 调用invokeCreateHooks -->循环cbs的create --> 因为listener存在所以进入updateDOMEListeners -->
- 拿到vnode.data.on --> (拿到click事件) --> 调用updateListeners --> (所以添加/删除事件的逻辑和组件添加/删除事件的逻辑是一样的,只是add不同) --> 既然一样就跳过了 -->
- add 添加原生DOM事件 --> 先调用withMacroTask 包装函数(和createFnInvoker) 类似 --> addEventListener 添加原生DOM事件 -->
-
跳回 invokeCreateHooks --> 跳回createElm --> 跳回patch --> 一直返回 child组件mount完成 --> 一直返回 --> createComponent
-
createComponent --> 执行完init钩子往下执行 -->
- 在component.init钩子的时候已经对vnode.componentInstance赋值 所以走进 initComponent函数 --> initComponent(vnode, insertedVnodeQueue);
- 在initComponent中拿到vnode.elm (拿到原生的DOM) -->
- if(isPatchable(vnode) == true ) 执行 --> 循环查找vnode.componentInstance._vnode --> 找到最低级的组件的实例的_vnode(就是找到最低级的vnode不是组件为止/也可以理解为找到第一个不是组件的vnode) [可能有点表述不清楚,多看几遍或许你能懂] --> 故找到button的VNode ,显然返回true (同理如果找到的是文本节点那就返回false) --> 往下执行 -->
- 执行和createElm后面一样的逻辑 执行 invokeCreateHooks(调用cbs创建的钩子) 和 setScope设置css作用域
- 执行invokeCreateHooks -第3个 --> updateDOMListeners --> updateListeners() --> 添加组件传入的click事件 --> 后面又是一样的跳过 --> 完成整个生成的过程
-
稍微梳理一下:
- 首先初始化component的时候先绑定组件事件(就是修改vm._event)
- 然后createChildren[button]绑定 button 的click事件 --> 所以执行 Button childed!! 并执行emit --> 执行 组件绑定的事件 --> 输出 Child select!
- 然后在组件上绑定的click 是最后绑定的所以 后执行 输出 --> Child clicked!