Vue Events模块原理分析
Vue中事件大致分为4类自定义事件DOM事件组件DOM事件组件自定义事件自定义事件主要由两部分组成事件存储器绑定事件,触发事件,解绑事件Vue的每个实例都会有一个_events对象,用来存放本实例上注册的自定义事件,绑定自定义事件的大致流程如下this.$on 绑定事件this.$emit 触发事件this.$off 解绑事件如果需要给组件绑定原生DOM事件,需要加上native这个修饰符。组件绑
Vue中事件大致分为4类
- 自定义事件
- DOM事件
- 组件DOM事件
- 组件自定义事件
自定义事件主要由两部分组成
- 事件存储器
- 绑定事件,触发事件,解绑事件
Vue的每个实例都会有一个_events
对象,用来存放本实例上注册的自定义事件,绑定自定义事件的大致流程如下
- this.$on 绑定事件
- this.$emit 触发事件
- this.$off 解绑事件
如果需要给组件绑定原生DOM事件,需要加上native
这个修饰符。组件绑定的DOM事件,在父实例解析完毕才开始挂载,遇到子元素是组件,然后去解析内部并生成DOM之后才用addEventListener
挂载。
绑定标签DOM事件
对标签绑定DOM事件,会生成如下的渲染函数
with(this) {
return _c('div', {
on: {
click: function() {}
}
})
}
根据渲染函数,执行得到VNode,可以看到事件被放到了data上
{
tag: 'div',
data: {
on: {
click() {}
}
}
}
在template解析得到VNode之后,接下来就会进行DOM的生成和挂载,而绑定事件就是发生在开始挂载,创建DOM之后的阶段,挂载是从Vue.prototype._update
开始的。绑定DOM事件核心方法就是updateDOMListeners
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]
);
}
}
}
自定义事件
Vue的自定义事件是存储在vm._events
中,这个作为自定义事件的存储器。它是在_init时候就初始化了
Vue.prototype._init = function(options) {
initEvents(vm);
}
function initEvents (vm) {
vm._events = Object.create(null);
}
下面介绍自定义事件4个核心方法
- $on
- $off
- $once
- $emit
Vue.prototype.$on = function(event, fn) {
var vm = this;
// 判断传入的event是否为数组
// 循环这个数组,给每一个event都绑定回调fn
if (Array.isArray(event)) {
for (var i = 0,l = event.length; i < l; i++) {
this.$on(event[i], fn);
}
}
else {
// 如果不是数组,直接添加回调fn
(vm._events[event] || (vm._events[event] = [])).push(fn);
}
// 为了链式调用
return vm
};
Vue.prototype.$once = function(event, fn) {
var vm = this;
// 重新包装一下回调
// 在回调内部触发$off
function on() {
vm.$off(event, on);
fn.apply(vm, arguments);
}
on.fn = fn;
vm.$on(event, on);
// 为了链式调用
return vm;
};
Vue.prototype.$emit = function(event) {
var vm = this;
var _events= event.toLowerCase();
var cbs = vm._events[_events];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
// 遍历回调,一个个调用
for (var i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args);
}
}
// 为了链式调用
return vm
};
Vue.prototype.$off = function(event, fn) {
var vm = this;
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
// 递归调用
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn);
}
return vm
}
var cbs = vm._events[event];
if (!cbs) return vm
if (!fn) {
vm._events[event] = null;
return vm
}
// 去掉特定的函数
if (fn) {
var cb;
var len = cbs.length;
// 遍历移除相应回调
while (len--) {
cb = cbs[len];
if (cb === fn || cb.fn === fn) {
cbs.splice(len, 1);
break
}
}
}
// 为了链式调用
return vm
};
给组件绑定自定义事件
当我们给组件绑定自定义事件时,会生成这样的VNode
{
tag: 'test',
componentOptions: {
listeners: {
click($event) {}
}
}
}
注册自定义事件的核心方法就是updateComponentListeners
function updateComponentListeners(
vm, listeners, oldListeners
) {
var name, cur, old;
for (name in listeners) {
cur = listeners[name];
old = oldListeners[name];
// 没有旧事件,就直接添加新事件
if (typeof old === "undefined") {
vm.$on(name, cur);
}
// 新事件和旧事件不一样,替换旧事件
else if (cur !== old) {
on[name] = cur;
}
}
// 移除旧事件
for (name in oldListeners) {
if (typeof listeners[name] === "undefined") {
vm.$off(name, oldOn[name]);
}
}
}
它内部其实也是调用了,自定义事件的$on, $off
更多推荐
所有评论(0)