• 【vue设计与实现】挂载和更新 6-事件冒泡与更新时机问题


    首先来看一个例子:

    const {effect, ref} = VueReactivity
    
    const bol = ref(false)
    
    effect(()=>{
    	// 创建node
    	const vnode = {
    		type: 'div',
    		props: bol.value?{
    			onClick: ()=>{
    				alert('父元素 clicked')
    			}
    		}:{}
    		children: [
    			{
    				type: 'p',
    				props: {
    					onClick: ()=>{
    						bol.value = true
    					}
    				},
    				children: 'text'
    			}
    		]
    	}
    	//渲染vnode
    	renderer.render(vnode,document.querySelector('#app'))
    
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这个例子中,vnode对象描述了一个div元素,并且该div元素具有一个p元素作为子节点。
    其中,div元素的props对象的值是由一个三元表达式决定。首次渲染时,由于bol.value的值为false,所以它的props值是一个空对象。
    而p元素具有click点击事件,并且当点击它时,事件处理函数会将bol.value的值设置为true.

    那么当首次渲染完成后,用鼠标点击p元素,会触发父级div元素的click事件处理函数执行吗?
    理论上来说,在首次渲染完成之后,由于bol.value的值为false,所以渲染器不会为div元素绑定点击事件。点击p冒泡到父元素div,因为div没有绑定click时间,所以什么都应该不发省。
    但是事实是父级div元素的click事件的事件处理函数竟然执行了。这其实跟更新机制有关。首先来分析下点击p元素时,到底发生了什么。
    点击p元素时,其身上的click函数会执行,bol.value的值改为true。由于bol是一个响应式数据,所以当值发生变化的时候,会触发副作用函数重新执行,在更新截断,渲染器会为父级div元素绑定click事件处理函数。当更新完成后,点击事件才从p元素冒泡到父级div元素。而这时div元素上已经绑定了click事件的处理函数。
    就是说,是因为更新操作发生在事件冒泡之前,即为div元素绑定事件处理函数发生在事件冒泡之前

    那么要怎么解决这个问题?
    可以发现,事件触发的时间要早于事件处理函数被绑定的时间。也就是说,当一个事件触发时,目标元素上还没有绑定相关的事件处理函数,那么可以这样:屏蔽所有绑定事件晚于时间触发时间的事件处理函数的执行。我们可以调整patchProps函数中关于事件的代码,如下:

    patchProps(el, key, prevValue, nextValue){
    	if(/^on/.test(key)){
    		const invokers = el._vei || (el.vei = {})
    		let invoker = invokers[key]
    		const name = key.slice(2).toLowerCase()
    		if(nextValue){
    			if(!invoker){
    				invoker = el._vei[key] = (e) => {
    					// e.timeStamp 是事件发生的时间
    					// 如果事件发生的时间早于事件处理函数绑定的时间,则不执行时间处理函数
    					if(e.timeStamp < invoker.attacher) return
    					if(Array.isArray(invoker.value)){
    						invoker.value.forEach(fn=>fn(e))
    					}else{
    						invoker.value(e)
    					}
    				}
    				invoker.value = nextValue
    				// 添加 invoker.attached属性,存储事件处理函数被绑定的时间
    				invoker.attached = performance.now()
    				el.addEventListener(name, invoker)
    			}else{
    				invoker.value = nextValue
    			}
    		}else if(invoker){
    			el.removeEventListener(name, invoker)
    		}
    	}else if(key === 'class'){
    		// 省略部分代码
    	}else if(shouldSetAsProps(el, key, nextValue)){
    		// 省略部分代码
    	}else{
    		// 省略部分代码
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    添加了invoker.attached属性,用来存储时间处理函数被绑定的时间。然后,在invoker执行的时候,通过时间对象的e.timeStamp获取事件发生的事件。最后,比较两者,如果事件处理函数被绑定的时间晚于事件发生的时间,则不执行该事件处理函数。

    这里,在关于事件方面,performance.now获取的是高精时间,但根据浏览器的不同,e.timeStamp的值会有所不同,既可以是高精时间也可能是非高精时间。因此要做兼容处理,但是在Chrome 49, Firefox 54, Opera 36之后的版本,e.timeStamp都是高精时间。

  • 相关阅读:
    【AI应用】NVIDIA Tesla V100S-PCIE-32GB的详情参数
    Java摆烂基础学习二~运算符
    中断下半部之 tasklet
    【面试:并发篇33:cas】原子更新器 原子累加器 缓存一致性问题
    【UDS 14229-1诊断服务内容详细解读】
    论文阅读 MAML (Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks)
    算法——滑动窗口
    java计算机毕业设计疫情下发热门诊管理系统MyBatis+系统+LW文档+源码+调试部署
    m4a格式怎么转换成mp3,非常简单
    Rust生态系统:探索常用的库和框架
  • 原文地址:https://blog.csdn.net/loyd3/article/details/125930232