上篇文档 React事件机制 - 源码概览(上)说到了事件执行阶段的构造合成事件部分,本文接着继续往下分析

批处理合成事件

入口是 runEventsInBatch

// runEventsInBatch
// packages/events/EventPluginHub.js
export function runEventsInBatch(events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,simulated: boolean,
) {if (events !== null) {eventQueue = accumulateInto(eventQueue, events);}const processingEventQueue = eventQueue;eventQueue = null;if (!processingEventQueue) {return;}if (simulated) {// react-test 才会执行的代码// ...} else {forEachAccumulated(processingEventQueue,executeDispatchesAndReleaseTopLevel,);}// This would be a good time to rethrow if any of the event handlers threw.rethrowCaughtError();
}
复制代码

这个方法首先会将当前需要处理的 events事件,与之前没有处理完毕的队列调用 accumulateInto方法按照顺序进行合并,组合成一个新的队列,因为之前可能就存在还没处理完的合成事件,这里就又有得到执行的机会了

如果合并后的队列为 null,即没有需要处理的事件,则退出,否则根据 simulated来进行分支判断调用对应的方法,这里的 simulated标志位,字面意思是 仿造的、假装的,其实这个字段跟 react-test,即测试用例有关,只有测试用例调用 runEventsInBatch方法的时候, simulated标志位的值才为true,除了这个地方以外,React源码中还有其他的很多地方都会出现 simulated,都是跟测试用例有关,看到了不用管直接走 else逻辑即可,所以我们这里就走 else的逻辑,调用 forEachAccumulated方法

// packages/events/forEachAccumulated.js
function forEachAccumulated<T>(arr: ?(Array<T> | T),cb: (elem: T) => void,scope: ?any,
) {if (Array.isArray(arr)) {arr.forEach(cb, scope);} else if (arr) {cb.call(scope, arr);}
}
复制代码

这个方法就是先看下事件队列processingEventQueue是不是个数组,如果是数组,说明队列中不止一个事件,则遍历队列,调用 executeDispatchesAndReleaseTopLevel,否则说明队列中只有一个事件,则无需遍历直接调用即可

所以来看下 executeDispatchesAndReleaseTopLevel这个方法:

// packages/events/EventPluginHub.js
const executeDispatchesAndReleaseTopLevel = function(e) {return executeDispatchesAndRelease(e, false);
};
// ...
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent,simulated: boolean,
) {if (event) {executeDispatchesInOrder(event, simulated);if (!event.isPersistent()) {event.constructor.release(event);}}
};
复制代码

executeDispatchesAndReleaseTopLevel又调用了 executeDispatchesAndRelease,然后 executeDispatchesAndRelease这个方法先调用了 executeDispatchesInOrder,这个方法是事件处理的核心所在:

// packages/events/EventPluginUtils.js
// executeDispatchesInOrder
export function executeDispatchesInOrder(event, simulated) {const dispatchListeners = event._dispatchListeners;const dispatchInstances = event._dispatchInstances;if (__DEV__) {validateEventDispatches(event);}if (Array.isArray(dispatchListeners)) {for (let i = 0; i < dispatchListeners.length; i++) {if (event.isPropagationStopped()) {break;}// Listeners and Instances are two parallel arrays that are always in sync.executeDispatch(event,simulated,dispatchListeners[i],dispatchInstances[i],);}} else if (dispatchListeners) {executeDispatch(event, simulated, dispatchListeners, dispatchInstances);}event._dispatchListeners = null;event._dispatchInstances = null;
}
复制代码

首先对拿到的事件上挂在的 dispatchListeners,也就是之前拿到的当前元素以及其所有父元素上注册的事件回调函数的集合,遍历这个集合,如果发现遍历到的事件的 event.isPropagationStopped()true,则遍历的循环直接 break掉,这里的 isPropagationStopped在前面已经说过了,它是用于标识当前 React Node上触发的事件是否执行了 e.stopPropagation()这个方法,如果执行了,则说明在此之前触发的事件已经调用 event.stopPropagation()isPropagationStopped的值被置为 functionThatReturnsTrue,即执行后为 true,当前事件以及后面的事件作为父级事件就不应该再被执行了

这里当 event.isPropagationStopped()true时,中断合成事件的向上遍历执行,也就起到了和原生事件调用 stopPropagation相同的效果

如果循环没有被中断,则继续执行 executeDispatch方法,这个方法接下来又一层一层地调了很多方法,最终来到 invokeGuardedCallbackImpl

// packages/shared/invokeGuardedCallbackImpl.js
let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(name: string | null,func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,context: Context,a: A,b: B,c: C,d: D,e: E,f: F,
) {const funcArgs = Array.prototype.slice.call(arguments, 3);try {func.apply(context, funcArgs);} catch (error) {this.onError(error);}
};
复制代码

关键在于这一句:

func.apply(context, funcArgs);
复制代码

funcArgs是什么呢?其实就是合成事件对象,包括原生浏览器事件对象的基本上所有属性和方法,除此之外还另外挂载了额外其他一些跟 React合成事件相关的属性和方法,而 func则就是传入的事件回调函数,对于本示例来说,就等于clickHandler这个回调方法:

// func === clickHandler
clickHandler(e) {console.log('click callback', e)
}
复制代码

funcArgs作为参数传入 func,也即是传入 clickHandler,所以我们就能够在 clickHandler这个函数体内拿到 e这个回调参数,也就能通过这个回调参数拿到其上面挂载的任何属性和方法,例如一些跟原生浏览器对象相关的属性和方法,以及原生事件对象本身(nativeEvent)

至此,事件执行完毕

这个过程流程图如下:

事件清理

事件执行完毕之后,接下来就是一些清理工作了,因为 React采用了对象池的方式来管理合成事件,所以当事件执行完毕之后就要清理释放掉,减少内存占用,主要是执行了上面提到过的位于 executeDispatchesAndRelease方法中的 event.constructor.release(event);这一句代码

这里面的 release就是如下方法:

// packages/events/SyntheticEvent.js
function releasePooledEvent(event) {const EventConstructor = this;invariant(event instanceof EventConstructor,'Trying to release an event instance into a pool of a different type.',);event.destructor();if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {EventConstructor.eventPool.push(event);}
}
复制代码

这个方法主要做了两件事,首先释放掉 event上属性占用的内存,然后把清理后的 event对象再放入对象池中,可以被后续事件对象二次利用

event.destructor();这句就是用于释放内存的,destructor这个方法的字面意思是 析构,也就表示它是一个析构函数,了解 C/C++的人应该对这个名词很熟悉,它一般都是用于 清理善后的工作,例如释放掉构造函数申请的内存空间以释放内存,这里的 destructor方法同样是有着这个作用

destructorSyntheticEvent上的方法,所以所有的合成事件都能拿到这个方法:

// packages/events/SyntheticEvent.js
destructor: function() {const Interface = this.constructor.Interface;for (const propName in Interface) {if (__DEV__) {Object.defineProperty(this,propName,getPooledWarningPropertyDefinition(propName, Interface[propName]),);} else {this[propName] = null;}}this.dispatchConfig = null;this._targetInst = null;this.nativeEvent = null;this.isDefaultPrevented = functionThatReturnsFalse;this.isPropagationStopped = functionThatReturnsFalse;this._dispatchListeners = null;this._dispatchInstances = null;// 以下省略部分代码// ...
}
复制代码

JavaScript引擎有自己的垃圾回收机制,一般来说不需要开发者亲自去回收内存空间,但这并不是说开发者就完全无法影响这个过程了,常见的手动释放内存的方法就是将对象置为 nulldestructor这个方法主要就是做这件事情,遍历事件对象上所有属性,并将所有属性的值置为 null

总结

React的事件机制看起来还是比较复杂的,我自己看了几遍源码又对着调试了几遍,现在又写了分析文章,回头再想想其实主线还是比较明确的,过完了源码之后,再去看 react-dom/src/events/ReactBrowserEventEmitter.js这个源码文件开头的那一段图形化注释,整个流程就更加清晰了

顺便分享一个看源码的技巧,如果某份源码,比如 React这种,比较复杂,代码方法很多,很容易看着看着就乱了,那么就不要再干看着了,直接写个简单的例子,然后在浏览器上打断点,对着例子和源码一步步调试,弄明白每一步的逻辑和目的,多调试几次后,基本上就能抓到关键点了,后续再通读源码的时候,就会流畅很多了

React事件机制 - 源码概览(下)相关推荐

  1. 11.QT事件机制源码时序分析(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109182406,本文继续解析QCoreApplication::sendEvent和Q ...

  2. 10.QT事件机制源码时序分析(中)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109162220,上篇文章已经说过,在Ubuntu18.04,QT的事件机制实际上是采用 ...

  3. 9.QT事件机制源码时序分析(上)

    通过上两篇博客https://blog.csdn.net/Master_Cui/article/details/109093845和https://blog.csdn.net/Master_Cui/a ...

  4. Qt 事件机制源码分析 QApplication exec 源码分析 多图超级详细

    前言: 不熟悉qt 源码结构的 可以先看这一篇 点我点我点我 写qt 的都知道 以下代码, 这段代码究竟的运行机制是怎么样的 咱们一步一步的看 QApplication a(argc, argv);Q ...

  5. 【保姆级】react17 事件机制源码解析

    写在前面 react17是一个过渡版本,最大的改动莫过于针对_事件机制的重构_.虽然现在react18版本已经发布了,但对事件机制这部分几乎是没有改动的,因此这里依然可以对17版本的这部分源码作一次解 ...

  6. Android View系列(二):事件分发机制源码解析

    概述 在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这 ...

  7. android SDK-25事件分发机制--源码正确解析

    android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...

  8. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  9. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

最新文章

  1. 光流估计:从传统方法到深度学习
  2. 《互联网运营智慧》终于上市销售了
  3. SpringBoot停车场管理系统(附源码)
  4. iphone7配置_西安苹果售后维修教您iphone7发热严重、耗电快怎么解决?
  5. 2017派卧底去阿里、京东、美团、滴滴带回来的面试题
  6. 求1!+2!+3!+....20!的值
  7. 使用GDAL打开裸数据(RAW)
  8. 您已登录了一个相同的QQ账号,不能重复登录”的解决办法
  9. 第3次作业:阅读《构建之法》1-5章
  10. 扫描计算机命令,Nmap常用命令之端口扫描
  11. HTML5 的新增特性
  12. ibm服务器维修检测报告,启创云小机(IBM POWER7)测试报告
  13. 十大web安全扫描工具
  14. 积水成渊之python——os.path.join()
  15. Android 系统启动 <System server> 服务 [3]
  16. 滑动窗口提取特征-torch.unfold的应用
  17. 网络存储技术Windows server 2012 (项目五 存储服务器的数据快照计划与故障还原)
  18. 2021-01-25广州大学ACM寒假训练赛解题心得
  19. 好玩有趣的代码注释,Emmm~~~
  20. ZJOI2016——一个蒟蒻的爆〇经历

热门文章

  1. 服务器物理内存高,服务器的物理内存高
  2. android 双时区,理查德米勒推出RM 11-02自动机芯双时区飞返计时码表
  3. python地图热力图是什么意思_python实现输入的数据在地图上生成热力图效果
  4. Java程序员面试如何超常发挥?
  5. 函数平移口诀_呆哥数学函数合集——函数的图形变换来啦【4】
  6. python打印表格_使用 Python 打印漂亮的表格,这两项基本功你可会?
  7. linux文件的权限模式,Linux文件权限和访问模式
  8. php 对数据转换成tree,PHP 把返回的數據集轉換成Tree樹
  9. 写给第十七届,来自十六届的感想与建议
  10. 智能车竞赛技术报告 | 智能车视觉 - 上海工程技术大学 - 萌鸡小队