您现在的位置是:亿华云 > 域名

Vue.js设计与实现之十三-渲染器的核心功能:挂载与更新02

亿华云2025-10-02 18:49:15【域名】7人已围观

简介1、写在前面在上篇文章中介绍了虚拟节点的挂载与更新,以及虚拟DOM节点上的属性设置,封装了新的卸载函数unmount。那么,虚拟节点上的事件又是如何处理的呢,同一个事件设置多个处理函数,同一个元素绑定

1、计实写在前面

在上篇文章中介绍了虚拟节点的现之渲染挂载与更新,以及虚拟DOM节点上的核心属性设置,封装了新的功能挂载更新卸载函数unmount。那么,计实虚拟节点上的现之渲染事件又是如何处理的呢,同一个事件设置多个处理函数,核心同一个元素绑定多个事件,功能挂载更新触发事件和绑定事件的计实时机问题应该如何处理?

2、事件的现之渲染处理

在Vue.js的事件处理先要解决的问题,就是核心如何在虚拟节点中描述事件,事件是功能挂载更新一种特殊的属性,在vnode.props对象中以字符串on开头的计实属性都被视作事件。

const vnode = {

type:"p",现之渲染

props:{

// 同一个事件多个事件处理函数

onClick:[

()=>{

//...

},

()=>{

//...

}

],

// 同一个元素绑定多个事件

onContextMenu(){

//...

}

},

children:"text"

}

renderer.render(vnode, document.querySelector("#app"));

在上面代码中,我们看到同一的核心DOM元素上可以绑定多个事件,同一个事件上又可以有多个事件处理函数。多次我们修改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] => {

//invoker.value是数组时,遍历逐个调用事件处理函数

if(Array.isArray(invoker.value)){

invoker.value.forEach(fn=>fn(e));

}else{

invoker.value(e);

}

}

invoker.value = nextValue;

el.addEventListener(name, invoker);

}else{

invoker.value = nextValue;

}

}else if(key === "class"){

//...

}else if(shouleSetAsProps(el, key, nextValue)){

//...

}else{

//...

}

}

}

在上面代码中,站群服务器先通过/^on/.test(key)检测元素上以on开头的属性,在绑定事件时伪造事件处理函数invoker

如果invoker不存在时,将invoker作为事件处理函数,缓存到el._vei属性中将真正的事件处理函数设置为invoker.value属性的值,伪造的事件处理函数invoker绑定到元素上

将el._vei的数据结构设计为一个对象,键即为事件名称,值为对应的事件处理函数,这样就不会出现事件覆盖的现象。当上面invoker.value的类型是数组时,数组中的每个元素都是一个独立的事件处理函数,且这些事件处理函数都能够正确绑定到对应元素上。

3、事件冒泡与更新时机问题

在事件处理中,需要注意处理事件冒泡和更新时机结合导致的问题,事件触发的时间会早于事件处理函数被绑定的时间。

const { effect, ref} = VueReactivity;

const bol = ref(false);

effect(()=>{

//创建vnode

const vnode = {

type:"div",

props:bol.value ? {

onClick(){

//...

}

}:{ },

children:[{

type:"p",

props:{

onClick(){

bol.value = true;

}

},

children:"pingping"

}]

}

//渲染vnode

renderer.render(vnode, document.querySelector("#app"));

})

在上面代码中进行理论分析,首次渲染后由于bol.value的初始值为false,云服务器提供商对此渲染器并不会给div元素绑定点击事件。在鼠标点击p元素后,bol.value的值变更为true,看到点击事件会从子元素p冒泡到父元素div上,但是div元素又没有绑定事件,因此啥也不发生。

但是,事实上在点击p元素时,父元素div的click事件触发了执行函数的执行。这是因为bol是个响应式数据,在点击p元素后,bol.value的值发生改变,会触发副作用函数的重新执行。而在更新阶段,渲染器会给div元素绑定click事件,在更新完后点击事件才从p元素冒泡到div元素。

触发事件的时机与事件绑定的时机的联系

在一个事件触发时,目标元素上还没有绑定相关的事件处理函数,因此屏蔽所有绑定事件时机要晚于触发时间的事件处理函数的云服务器执行。

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.timeStamp是事件发生的时间,如果事件触发的时机早于事件绑定的时间,则不执行事件处理函数

if(e.timeStamp < invoker.attached) return;

//invoker.value是数组时,遍历逐个调用事件处理函数

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(key === "class"){

//...

}else if(shouleSetAsProps(el, key, nextValue)){

//...

}else{

//...

}

}

}

在上面代码中,给伪造的事件处理函数添加了invoker.attached属性,用于存储事件处理函数被绑定的时间。在invoker执行的时候,通过事件对象e.timeStamp获取事件发生的时间,比较两者的时间,如果事件触发的时机早于事件绑定的时间,则不执行事件处理函数。

4、写在最后

在本文中主要讨论了事件的处理,介绍了在虚拟节点上绑定事件,如何绑定和更新事件。同时,还介绍了如何处理触发事件与更新时机的问题,屏蔽所有绑定事件时机要晚于触发时间的事件处理函数的执行。

很赞哦!(5332)