写文章不容易,点个赞呗兄弟
专注 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 事件到这里就完成了,希望对大家有所帮助