写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
【Vue原理】Event - 源码版 之 绑定标签DOM事件
这里的绑定DOM事件,是指绑定原生标签的DOM 事件
因为组件也是可以绑定原生DOM事件的,不过并不是在原生标签上绑定,而是直接在组件上绑定的,这部分内容会其他文章说明
或者你可以看看白话版先了解下Event
【Vue原理】Event - 白话版
怎么解析
由于解析不是本内容的重点,所以在这里就不谈怎么解析的了,只说一个结果就好了
现在有这么一个模板
模板被解析成这样的渲染函数
渲染函数执行之后,得到这样的 VNode
你可以看到,事件被存放到了 vnode.data 上
Vnode 有疑惑的可以看介里
【Vue原理】VNode - 源码版
怎么绑定
既然模板已经被解析完成了,下一步就是开始绑定了
好的,继续来走流程
在 template 解析得到 Vnode 之后,下面就会进行DOM生成挂载
而绑定事件,就发生在开始挂载,创建DOM 之后 的阶段
挂载时从 Vue.prototype._update 这个函数开始的
挂载的流程,可以看看这篇文章
从模板到DOM的简要流程
1、开始挂载
VNode创建完毕,传入 Vue.prototype._update 这个方法中,进行比对新旧VNode
然后生成DOM挂载页面
其中需要生成DOM,调用的方法是 createElm
2、创建DOM
创建DOM,在Vue 中调用的是 createElm 这个方法
看过以前的文章的,都知道这个函数的作用是
根据 vnode 生成DOM,并且进行挂载
而在 createElm 中,会调用一个函数去 处理模板上相关的数据
比如处理属性,类名,style 之类的,其中DOM事件也是在这里处理的
这个函数就是 invokeCreateHooks,继续往下看
function createElm(vnode) {
// ....处理组件
// ....生成标签对应dom
// ....递归遍历子节点
invokeCreateHooks(vnode);
// ....插入DOM 节点
}
3、处理数据
上面源码中出现的 invokeCreateHooks 这个方法是用来处理数据的
每种数据(style,class等),都有一个专门的函数去进行处理
而 invokeCreateHooks,就是负责执行每种数据的处理函数,很简单,就是一个单纯遍历执行的过程
其中就包括处理 DOM 事件的函数,便是 updateDOMListeners
function invokeCreateHooks(
vnode
) {
/**
* 执行的函数包括下面这么多
* cbs = [
* create:[
* updateAttrs, updateClass,
* updateDOMListeners, updateDOMProps,
* updateStyle, create, updateDirectives
* ]
* ]
**/
for (var b = 0; b < cbs.create.length; ++b) {
// 其中会调用 updateDOMListeners
// emptyNode 是空节点,因为这里是初始化才会调用的
// 所以旧节点是空节点
cbs.create[b](emptyNode, vnode);
}
....
}
下面看下 处理DOM 事件的函数
4、绑定DOM事件
简化的源码,看起来顺眼多了,主要逻辑一清二楚,主要就是绑定事件和解绑事件,你看下喽,挺简单的
function updateDOMListeners(oldVnode, vnode) {
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
var target = vnode.elm;
// 遍历绑定的事件
for (name in on) {
newHandler = on[name];
oldHandler = oldOn[name];
// 没有旧事件,就直接添加新事件
if (typeof oldHandler === "undefined") {
// 给事件回调包装一层
target.addEventListener(name, function(){
on[name]() // 执行保存在vnode的事件
});
}
// 新事件和旧事件不一样,替换旧事件
else if (newHandler !== oldHandler) {
on[name] = newHandler;
}
}
// 移除旧事件
for (name in oldOn) {
// 旧事件不存在新事件中,直接移除
if (typeof on[name] === "undefined") {
target.removeEventListener(
name, oldOn[name]
);
}
}
}
看看绑定函数和 移除函数,就只是简单使用 addEventListener 和 removeEventListener,我没看之前还以为 Vue 写了很多兼容,没想到就是这么简单完成这个功能
有点惊讶,反正简单也好吧,哈哈哈,简单看着就是苏胡啊~~
绑定逻辑很简单
1、新旧事件相同,替换旧事件
2、新事件不存在旧事件中,绑定新事件
3、旧事件不存在新事件中,解绑旧事件
其中会给回调事件函数包装一层函数,然后在内部执行绑定的回调,包装一层的原因是,为了在回调中做点其他操作(比如宏微任务的处理等,这里为了简单去掉了)
并且旧事件回调改了的时候,就更加方便了,不用解绑再绑定,直接把执行的事件回调 on[name]替换掉就ok了
好的,原生标签绑定DOM 事件到这里就完成了,希望对大家有所帮助