VUE2/3源码对事件绑定的巧妙处理
简介
传统的给dom更改绑定事件处理函数,通常都是先remove,再add,而在我最近看Vue源码之后,发现Vue当中对事件绑定的处理非常巧妙。废话不多说,直接上代码。
源码处理流程对比
vue2
首先在vue2源码当中找出对应的处理函数
updateDOMListeners
这个函数当中,首先对事件们做了normalizeEvents标准化处理,然后放到updateListeners里面做更新绑定逻辑的处理
updateListeners
normalizeEvent
首先在normalizeEvent,通过cached函数对event的一些信息(事件名称对应的)做了闭包缓存处理
顺便一提cached工厂函数
cached 工厂函数,接受类型为 【入参string,返回泛形R的函数】作为参数,返回另一个 【入参string,返回泛形R的函数】
可以看到在这里会对目前的vm的事件处理函数用createFnInvoker进行包裹
createFnInvoker
原本的函数就被套用上一层invoker函数,被放到fns属性当中去执行
最后通过add函数去绑定
add
remove
解除的时候,通过removeEventListener进行解绑
在更新绑定事件函数的时候,回到updateListeners,是直接对invoker函数的fns指向进行修改,因此无需重新addEvnetListener进行换绑。达到了优化效果
vue3
vue3当中的入口就是createApp
在patchProps当中的patchEvent环节
patchEvent
createInvoker
这里是重点了,可以看到相对于vue2的invoker,vue3的invoker同样是一个函数,同样的是vue3是直接修改的invoker的value,vue2是修改的fns,其实本质上是一样的,这样造成的结果是:之前绑定的事件处理函数invoker却并没有发生改变!
addEventListener
removeEventListener
小测试
通过模拟vue和传统做法,进行1000000 -1 次事件更新,比较最后的运行时间.
<template>
<button id="invoker">Vue绑定</button>
<button id="tradition">传统绑定</button>
</template>
<script>
const invokerBTN = document.getElementById('invoker')
const traditionBTN = document.getElementById('tradition')
const tryTimes = 1000000
const eventInvoker = {
'click':()=>{
console.log('clicked!');
}
}
function testTra(){
console.time('traditional event handler')
let j = 0
while(j<tryTimes){
for(i in eventInvoker){
traditionBTN.addEventListener(i,eventInvoker[i])
traditionBTN.removeEventListener(i,eventInvoker[i])
}
j++
}
console.timeEnd('traditional event handler')
}
// vei = vue event invokers
const vei = {}
function testInvoke(){
console.time('vue event handler')
let j = 0
while(j<tryTimes){
for(i in eventInvoker){
let invoker = vei[i]
if(!invoker){
invoker = (e)=>{
invoker.value && invoker.value(e)
}
invoker.value = eventInvoker[i]
invokerBTN.addEventListener(i,invoker)
vei[i] = invoker
}else{
invoker.value = eventInvoker[i]
}
}
j++
}
console.timeEnd('vue event handler')
}
testTra()
testInvoke()
</script>
运行结果
可以看到差别还是蛮大的
总结
Vue源码当中对事件的绑定处理,是通过原生api【addEventListener】绑定对应的invoker函数,在invoker内部执行真正的事件处理函数,并且将内部处理函数通过指针暴露出来,从而实现更新时只需更改内存指向即可。
转载自:https://juejin.cn/post/7206576861051584573