Skip to content
This repository has been archived by the owner on Nov 12, 2024. It is now read-only.

magix的事件代理 #14

Open
xinglie opened this issue Jul 30, 2016 · 2 comments
Open

magix的事件代理 #14

xinglie opened this issue Jul 30, 2016 · 2 comments

Comments

@xinglie
Copy link
Member

xinglie commented Jul 30, 2016

先说普通事件代理,以jQuery为例

$(document.body).on('click','p',function(e){

});

这样其实是为document.body上绑定一个click事件,当鼠标在页面上点击时,看点击的元素是否是p元素,如果是则触发,不是则向从当前节点向上查找到符合的p元素,一直查到body。这里其实是有性能问题的(jQuery已经弃用的live),稍后我们再讨论并给出解决方案。

事件代理其实就是把事件处理函数绑定在父级,通过事件冒泡的特点,识别触发事件的元素是否是自已期望的元素。对,重点是这个事件冒泡

Magix中的事件代理的过程是这样的:把相应的事件绑定在父节点后,当事件发生在父节点里,父节点的事件处理函数被调用后,从触发事件的节点(target)开始,判断节点是否有mx-eventType属性,如果有则停下来调用相应view的事件处理函数,如果没有则向上查找,直到父节点

再来看Magix1.0之前的事件代理方案
当前view使用的事件都统一绑定到vframe根节点上,而vframe又可以嵌套,比如嵌套后的结构如下:

Delegate Event

我们的事件是绑定在vframe上的,当vframe2vframe1绑定相同类型的事件时,比如click,而vframe2viewvframe1view具有相同的事件处理函数时,比如selectAll,那么问题就来了:鼠标点击在vframe2的SelectAll checkbox上时,vframe2先处理,而同时事件冒泡,冒泡到vframe1上时,vframe1selectAll方法同样会被触发。

这里因为vframe1节点与vframe2节点都绑定了click事件,而由于事件冒泡的原因,同时事件类型及方法名又一致,当点击在vframe2内部时,vframe2处理完事件向上冒泡到vframe1,导致vframe1又处理了一遍,那或许你会说为什么不取消事件冒泡呢?

因为在我们的项目中,我们有可能引入其它组件,比如弹出日历等,需要鼠标点击在页面其它位置时日历需要隐藏消失。所以我们不能直接就把事件冒泡取消,否则日历也无法隐藏(像日历这种通常是绑定在document,需要相关事件冒泡到document)
问题出来了总得有个解决办法

第1个解决方案:

明确事件的处理vframe,类似上图,当鼠标点击在vframe2时,当vframe2处理完事件后,给打一个标记,标明当前事件是被处理过的,当vframe1收到后,先判断是否是处理过的,如果是处理过则不再理会
说到这里,大家可以想象一下,如果嵌套的vframe比较多时,鼠标点击在最内部的vframe时,由于事件冒泡,冒泡到每一层的vframe时,这一层的vframe都需要对事件做一次判断,而这些判断其实是不必要的,因为事件已经被内部vframe处理了,为什么还在再次判断?这地方多少会有些性能损耗

第2个解决方案:

把代理节点提高到body上,如果事件代理在body上而不是vframe上,把注册在vframe上相同的监听统一合并到body上,这样vframe节点不需要注册任何事件,对于嵌套的vframe,也是统一在body上处理事件,不需要每一层vframe的判断了。

该方案所有事件在绑定时,不管什么事件类型都绑定的是同一个事件处理函数,在这个事件处理函数内部再决定调用哪一个view的处理函数。内部针对magix的特点,对事件这块再特殊优化,从而提升性能,原理接下来讨论

回到开头我们的那个问题(jQuery live):事件是绑定在body上的,每当事件发生时,我们都要从发生事件的节点向上查找带有mx-eventType标识的节点,直到body。那我们来看一种情况:

Delegate Event

假设html结构是这样的:

<div>
    div1
    <div>
        div2
        <button mx-click="save()">Save</button>
    </div>
</div>

document.body注册了一个click事件,当鼠标点击在button上时,target即符合我们的要求(带有mx-click属性),所以不再向上查找,把button转交给相应的vframe去处理,而如果点击在button外部时,由于div不带mx-click属性,则会一直向上查找,直到body,而如果这种嵌套比较深的话,每次点击在button外部时,都需要从里至外的查找到body,性能上肯定不乐观,如何提升这块的性能 ?

我们发现不管是点击在div2内部还是div1内部,从事件触发的节点到body这条路径上是没有带mx-eventType属性的节点的,所以在向上查找的过程中,会把整条路径上的节点都记录下来,如果到body还未找到带mx-eventType的节点,则会把这些节点都标记上不需要处理click事件。

表明从这个节点向上是没有带mx-eventType属性的节点的,当下次再点击时,首先看当前这个节点是否已经被标识,如果被标识没有eventType类型的事件,则不再向上查找

由于Maigx事件本身是代理进行的,直接在期望的节点上写mx-event即可,最好不要出现类似这样的代码:

<div>
    <ul mx-click="delegateLiClick()">
        <li>A</li>
        <li>B</li>
        <li>C</li>
    </ul>
</div>

直接在li上面写mx-click即可,如果是在ul节点上,Magix本身需要从事件触发的节点向上找到带mx-eventType,开发者还需要从target再来判断一次事件触发节点是否是期望的li,从而拉低了性能。

经过上述方案后,对于高频事件比如mousemove也能很好的应对,不至于鼠标在页面内快速移动而造成CPU瞬间上升

@xinglie
Copy link
Member Author

xinglie commented Oct 18, 2016

在真实绑定事件的时候,Magix对接的是类库的事件绑定,如使用jQuery类库则调用jQuery的事件绑定机制进行绑定。
Magix在绑定的时候有如下优化:

  1. 无论多少个view,也无论每个view绑定多少个不同类型的事件,Magix只会调用一次类库事件的绑定方法。即不同的事件类型绑定的也是同一个Magix内部的事件处理方法
  2. 当某个事件发生时,Magix内部事件处理方法根据Magix的view嵌套关系特点,快速的找到对应的处理view,进而调用对应的方法

用图表示如下:
image
简单聊下jQuery的事件绑定,如图,当我们绑定click1,click2,click3,三个事件处理函数时,调用jquery的事件绑定方法,它内部会用把click1,click2,click3放到这个eventList数组里,而真实绑定的则是eventHandle的内部方法,当事件发生时,再循环调用eventList里面的回调函数
image
Magix的事件绑定,没有eventList来存储回调函数,而是当事件发生时,根据view之间的关系,快速寻找到对应的view。同时可避免不必要的检测,如点击在vf4区域内时,vf3则不会被检测

@sprying
Copy link

sprying commented Dec 18, 2017

我们发现不管是点击在div2内部还是div1内部,从事件触发的节点到body这条路径上是没有带mx-eventType属性的节点的,所以在向上查找的过程中,会把整条路径上的节点都记录下来,如果到body还未找到带mx-eventType的节点,则会把这些节点都标记上不需要处理click事件。

这段话没能理解╮(╯_╰)╭

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants