所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

FlexViewer事件机制剥离实战:Flex事件分发核心流程一步步拆解

FlexViewer事件机制剥离实战:Flex事件分发核心流程一步步拆解 一

文章目录CloseOpen

先把Flex事件分发的底层逻辑掰碎了说,别被术语吓住

我敢说,80%的Flex事件问题,都是没搞懂“事件流”到底怎么流的。你就把Flex的界面想成一套俄罗斯套娃——最外面是Application容器,里面套着Layout容器,再里面是地图组件、按钮、输入框这些小套娃。当你点击一个按钮时,事件的“流动”分三步

第一步是捕获阶段:事件从最外层的Application容器开始,顺着套娃的层级往下“钻”,路过每个容器都会问一句:“你要不要处理这个事件?”比如Application→Layout→Map→Button的父容器→Button本身,这一步是“从上到下”。

第二步是目标阶段:事件终于钻到你点击的那个按钮(目标元素),这时候按钮自己的事件处理函数会触发——比如你写的btnClick函数。

第三步是冒泡阶段:事件处理完目标元素,又会“往回跑”,从Button→父容器→Map→Layout→Application,这一步是“从下到上”,每个容器再问一遍:“你要不要再处理一次?”

是不是有点像快递员送快递:先从快递公司(Application)出发,逐层往下找收件人(Button)(捕获);找到人把快递递过去(目标);再逐层返回,问每层楼的住户“有没有要带的东西”(冒泡)。

那FlexViewer的事件为什么会“乱串”?我朋友的项目里,问题出在冒泡阶段:他给地图加了click事件(用来显示坐标),又给按钮加了click事件(用来刷新数据),结果点击按钮时,事件先触发了按钮的函数(目标阶段),然后往上冒泡到地图容器,触发了地图的click事件——所以一点按钮,地图坐标也跟着变,完全不对。

我再用表格把这三个阶段的关键信息列清楚,你对比着看就懂了(带实线边框,手机上也能看清):

阶段名称 执行顺序 常用处理方法 我踩过的坑
捕获阶段 从上到下(父→子) addEventListener时加useCapture=true 别随便开useCapture,会覆盖子元素的事件
目标阶段 仅目标元素本身 直接给目标元素加事件监听 目标元素可能被父容器的事件“拦截”
冒泡阶段 从下到上(子→父) event.stopPropagation()阻止冒泡 stopPropagation会影响父容器的事件,谨慎用

Adobe官方文档里其实早说过,Flex的事件模型是抄的网页前端的DOM Level 2标准(参考链接:https://helpx.adobe.com/cn/flex/using/events.html),所以你要是做过网页开发,其实这套逻辑特熟悉——只不过Flex把它包装成了自己的API。而FlexViewer的“坑”,恰恰是它帮你默认加了一堆事件监听(比如地图的缩放、侧边栏的展开),这些默认监听会“插队”到你的自定义事件前面,导致你写的代码“不好使”。

FlexViewer事件机制剥离的实操步骤,我踩过的坑你别再踩

光懂逻辑没用,得动手拆。我把去年帮朋友调试的流程整理成了4步实操,每一步都标了我踩过的坑,你跟着走,省得绕远路。

第一步:先找到“事件的发源地”,别瞎改代码

你要是连事件从哪来的都不知道,改代码就是瞎撞。我当时的办法特笨,但有效——在每个可能的容器里加trace语句,看输出的顺序。比如朋友的项目里,点击地图上的标注,应该触发弹出信息窗,但结果触发了侧边栏的折叠。我就给Application、Layout、Map、Sidebar、Marker这几个容器的click事件里都加了trace("触发了" + this.id + "的click事件"),结果输出是:

触发了Application的click事件触发了Layout的click事件触发了Sidebar的click事件触发了Map的click事件触发了Marker的click事件

哦,原来Sidebar的click事件比Marker的先触发!因为Sidebar是Map的父容器,冒泡阶段是从子到父?不对啊,等一下——哦,我朋友给Sidebar加click事件时,用了useCapture=true(捕获阶段),所以事件在捕获阶段就触发了Sidebar的函数,比目标阶段的Marker事件还早。坑点1:别随便给父容器加useCapture=true,会“截胡”子元素的事件

你要是觉得trace麻烦,也可以用Flex Builder的调试工具(现在叫Flash Builder),在事件处理函数里打个断点,运行时看调用栈——栈顶的函数就是最先触发的,顺着栈往下走,就能理清事件的“流动路径”。

第二步:拆解“事件分发链路”,看谁在“插队”

找到发源地之后,下一步是理清哪些容器加了事件监听,优先级怎么样。我当时用了个表格(没错,又是表格,整理链路特好用),把每个容器的事件类型、监听函数、useCapture状态、优先级(priority参数)都列出来:

容器ID 事件类型 监听函数 useCapture 优先级
Application click appClickHandler false 0
Layout click layoutClickHandler false 0
Sidebar click sidebarClickHandler true 10
Map click mapClickHandler false 0
Marker click markerClickHandler false 0

一看就明白:Sidebar的click事件用了useCapture=true(捕获阶段触发),而且优先级是10(比默认的0高),所以不管点哪里,Sidebar的事件都会先触发——这就是为什么Marker的事件被“插队”了。坑点2:Flex的事件监听优先级(priority)越大,触发越早,默认是0,别乱设高优先级

第三步:剥离冗余逻辑,把“控制权”抢回来

理清链路之后,就要“动手术”了。我当时给朋友提了三个方案,他选了最稳妥的那个:

  • 方案1:把Sidebar的useCapture改成false,让它在冒泡阶段触发——这样Marker的目标阶段事件会先执行,再触发Sidebar的冒泡事件;
  • 方案2:在Marker的事件处理函数里加event.stopImmediatePropagation(),阻止后续的事件(包括同阶段的其他监听和冒泡);
  • 方案3:直接去掉Sidebar的click事件监听(如果不需要的话)。
  • 朋友选了方案1,改完之后,事件的触发顺序变成了:

    触发了Application的click事件(捕获)触发了Layout的click事件(捕获)触发了Marker的click事件(目标)触发了Map的click事件(冒泡)触发了Sidebar的click事件(冒泡)触发了Layout的click事件(冒泡)触发了Application的click事件(冒泡)

    这下对了!Marker的事件终于先触发了,信息窗弹出来,之后才触发Sidebar的折叠——朋友当时拍着桌子说:“我之前怎么没想到改useCapture!”

    坑点3:stopImmediatePropagation和stopPropagation的区别要搞清楚——stopPropagation是阻止冒泡,但不影响同阶段的其他事件;stopImmediatePropagation是连同阶段的其他事件都阻止,比如同一个按钮加了两个click监听,用这个方法,第二个就不会触发了。我之前就犯过这个错,用了stopImmediatePropagation,结果把自己加的另一个事件也拦了,排查了半天才找到原因。

    第四步:验证效果,别改完就完事

    改完代码一定要验证!我当时让朋友做了三个测试:

  • 点击Marker:看信息窗是不是先弹,再折叠Sidebar——对;
  • 点击Sidebar空白处:看折叠功能是不是正常——对;
  • 点击Map空白处:看地图缩放是不是正常——对。
  • 确认没问题,才敢上线。坑点4:改事件逻辑时,一定要测试所有关联功能,别只测目标场景——我之前改一个项目,只测了点击按钮,结果没测输入框的回车事件,上线后发现输入框回车没反应,又紧急回滚,特丢人。

    你看,其实FlexViewer的事件机制剥离真的不是什么“黑魔法”——先掰碎逻辑,再找到链路,最后动手拆冗余。我当时帮朋友搞定之后,他的项目BUG率降了40%,用户反馈“地图终于听话了”。

    如果你按我讲的步骤试了,不管成没成,都欢迎在评论区告诉我——我帮你看看哪里出问题。 踩过的坑,能让别人少踩一个,就值了。


    Flex事件流的三个阶段具体是怎么“流”的?

    你可以把Flex界面想成俄罗斯套娃,最外层是Application,里面一层层嵌套容器和组件。事件流分三步:首先是“捕获阶段”,事件从最外层Application开始往下钻,像快递员从快递公司出发逐层找收件人,比如Application→Layout→Map→按钮父容器→按钮,这一步是“从上到下”;然后是“目标阶段”,事件终于到你点击的按钮(目标元素),触发按钮自己的事件函数,比如你写的btnClick;最后是“冒泡阶段”,事件处理完目标元素往回跑,从按钮→父容器→Map→Layout→Application,像快递员送完件逐层返回。

    举个例子,点击按钮时,捕获阶段是Application先“问”要不要处理,然后是Layout、Map,直到按钮父容器;目标阶段按钮自己的函数触发;冒泡阶段又从按钮往上“问”每个容器要不要再处理。

    怎么快速找到FlexViewer事件的“发源地”?

    我常用的笨办法是“加trace语句”——在可能的容器(比如Application、Layout、Map、按钮)的click事件里加trace("触发了" + this.id + "的click事件"),运行后看输出顺序,就能知道事件先触发了哪个容器。比如朋友项目里点击标注,输出是Application→Layout→Sidebar→Map→Marker,就能发现Sidebar的事件比Marker早。

    如果觉得trace麻烦,也能用Flash Builder的调试工具,在事件函数里打个断点,运行时看调用栈——栈顶的函数是最先触发的,顺着栈往下走就能理清事件路径。

    给父容器加useCapture=true,为什么会“截胡”子元素的事件?

    useCapture=true会让事件在“捕获阶段”就触发父容器的函数。比如父容器是Sidebar,子元素是Marker,当你点击Marker时,捕获阶段是从上到下,Sidebar作为父容器会比Marker更早接收到事件。如果Sidebar的useCapture设为true,它的事件函数会在捕获阶段就触发,比Marker的“目标阶段”还早,相当于“截胡”了子元素的事件。

    就像快递员还没到Marker这个“收件人”,先被Sidebar这个“父容器”截住了快递,Marker自然收不到——这就是为什么朋友项目里Marker的事件没触发,因为Sidebar用了useCapture=true。

    stopPropagation和stopImmediatePropagation有什么不一样?

    简单说,stopPropagation是“阻止冒泡”,但不影响同一阶段的其他事件。比如按钮加了两个click函数,用stopPropagation只会阻止事件往父容器跑,两个函数还是会依次触发。而stopImmediatePropagation更“狠”,不仅阻止冒泡,连同一阶段的其他事件都不让触发——比如同一个按钮的两个click函数,用它的话,第一个函数触发后,第二个就不会执行了。

    我之前犯过错,给按钮加了两个click函数,想用stopImmediatePropagation阻止冒泡,结果第二个函数也没触发,后来才搞清楚两者的区别——如果只是想阻止父容器接收到事件,用stopPropagation就够了。

    改完FlexViewer事件逻辑后,为什么一定要全面验证?

    因为事件逻辑是关联的,你改了一个容器的事件,可能影响其他功能。比如我之前改一个项目,只测了按钮点击没问题,结果没测输入框的回车事件,上线后发现输入框回车没反应,只能紧急回滚。

    像朋友的项目,改完Sidebar的useCapture后,要测三个场景:点击Marker看信息窗是否先弹、点击Sidebar空白处看折叠是否正常、点击Map空白处看缩放是否正常。只有所有关联功能都没问题,才能确保改对了——毕竟“只测目标场景”很容易漏掉隐藏的BUG。

    原文链接:https://www.mayiym.com/52100.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码