Skip to content

前言

React 18.3.1 使用合成事件的核心原因

1. 统一浏览器行为

不同浏览器(如 IE、Chrome、Firefox)对原生事件(如 focus、scroll)的实现差异较大,合成事件通过标准化 API 屏蔽底层差异,确保跨浏览器一致性。

2. 优化事件系统架构

React 需要将事件逻辑与组件生命周期、虚拟 DOM 更新深度绑定,合成事件为 React 提供了可控制的事件流机制。


合成事件的核心优势

1. 跨浏览器一致性

  • 标准化 API: 无论用户使用何种浏览器,合成事件(如 onClick、onChange)的行为和属性完全一致。
  • 修复浏览器差异: 例如,onScroll 在原生事件中不冒泡,但 React 通过合成事件模拟冒泡逻辑,使其行为更符合开发者直觉。

2. 性能优化

  • 事件委托(Event Delegation): React 17+ 将事件统一绑定到应用根容器(如 #root),而非每个 DOM 节点,大幅减少内存占用和绑定开销。
  • 自动清理监听器: 组件卸载时,React 自动解除相关事件监听,避免内存泄漏(无需手动 removeEventListener)。

3. 对 React 生态的深度集成

  • 与组件生命周期同步: 合成事件的处理逻辑会考虑组件状态(如异步更新的 setState),避免因事件触发时组件已卸载导致的错误。
  • 支持批量更新: 在合成事件回调中的状态更新会自动合并为批量更新,减少不必要的渲染。

4. 未来兼容性与扩展性

  • 渐进式升级: 合成事件系统使 React 能独立于浏览器演进,例如 React 17 将事件委托目标从 document 改为根容器,解决微前端场景下的多应用冲突。
  • 自定义事件支持: 开发者可通过合成事件系统扩展自定义事件(如长按、手势),无需依赖第三方库。

5. 开发体验提升

  • 异步访问事件属性: React 17+ 移除了事件池(Event Pooling),开发者无需调用 e.persist() 即可在异步代码中直接使用事件对象。
  • 更直观的冒泡行为: 修正了原生事件中 onFocus/onBlur 不冒泡的问题,改用 onFocusIn/onFocusOut 模拟冒泡,逻辑更统一。

整体流程图

svg图片,可以在新标签页中打开图片查看大图

Image


合成事件注册listenToAllSupportedEvents

ReactDOM.createRoot()

一系列对象的初始化和事件初始化

javascript
function createRoot(container, options) {
  //...
  var root = createContainer(
          container,
          ConcurrentRoot,
          null,
          isStrictMode,
          concurrentUpdatesByDefaultOverride,
          identifierPrefix,
          onRecoverableError
  );
  markContainerAsRoot(root.current, container);
  //...
  listenToAllSupportedEvents(rootContainerElement);
  return new ReactDOMRoot(root);
}

从上面的初始化代码可以知道,我们会触发listenToAllSupportedEvents()进行事件的注册

对于非 selectionchange 事件,会判断是否是委托事件,如果是非委托事件,则触发

  • listenToNativeEvent(domEventName, false, rootContainerElement)
  • listenToNativeEvent(domEventName, true, rootContainerElement)

否则就只触发

  • listenToNativeEvent(domEventName, true, rootContainerElement)

对于selectionchange 事件,触发

  • listenToNativeEvent("selectionchange", false, ownerDocument) 绑定到 ownerDocument 上,而不是上面的 rootContainerElement

最终触发的还是 listenToNativeEvent() 方法

ts
function listenToAllSupportedEvents(rootContainerElement) {
  if (!rootContainerElement[listeningMarker]) {
    rootContainerElement[listeningMarker] = true;
    allNativeEvents.forEach(function (domEventName) {
      // We handle selectionchange separately because it
      // doesn't bubble and needs to be on the document.
      if (domEventName !== "selectionchange") {
        if (!nonDelegatedEvents.has(domEventName)) {
          listenToNativeEvent(domEventName, false, rootContainerElement);
        }
        listenToNativeEvent(domEventName, true, rootContainerElement);
      }
    });
    var ownerDocument =
      rootContainerElement.nodeType === DOCUMENT_NODE
        ? rootContainerElement
        : rootContainerElement.ownerDocument;

    if (ownerDocument !== null) {
      // The selectionchange event also needs deduplication
      // but it is attached to the document.
      if (!ownerDocument[listeningMarker]) {
        ownerDocument[listeningMarker] = true;
        listenToNativeEvent("selectionchange", false, ownerDocument);
      }
    }
  }
}

而在 listenToNativeEvent() 方法中,其中触发的是 addTrappedEventListener()

addTrappedEventListener()主要做两件事:

  1. createEventListenerWrapperWithPriority(): 合成事件的监听事件的构建 listener
  2. 触发原生API target.addEventListener(eventType, listener, {...}) 进行 targetContainer各种原生事件 的绑定

addEventCaptureListenerWithPassiveFlagaddEventCaptureListeneraddEventBubbleListenerWithPassiveFlagaddEventBubbleListener本质都是target.addEventListener(),只是参数不同而已

ts
function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
  var eventSystemFlags = 0;
  if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE;
  }
  addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
function addTrappedEventListener() {
  var listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags
  );

  var isPassiveListener = undefined;
  if (passiveBrowserEventsSupported) {
    if (
      domEventName === "touchstart" ||
      domEventName === "touchmove" ||
      domEventName === "wheel"
    ) {
      isPassiveListener = true;
    }
  }
  targetContainer = targetContainer;
  if (isCapturePhaseListener) {
    if (isPassiveListener !== undefined) {
      addEventCaptureListenerWithPassiveFlag(
        targetContainer,
        domEventName,
        listener,
        isPassiveListener
      );
    } else {
      addEventCaptureListener(targetContainer, domEventName, listener);
    }
  } else {
    if (isPassiveListener !== undefined) {
      addEventBubbleListenerWithPassiveFlag(
        targetContainer,
        domEventName,
        listener,
        isPassiveListener
      );
    } else {
      addEventBubbleListener(targetContainer, domEventName, listener);
    }
  }
}

createEventListenerWrapperWithPriority() 中,会根据事件名称去返回不同的方法:

  • dispatchDiscreteEvent
  • dispatchContinuousEvent
  • dispatchEvent

这里的返回的 listenerWrapper.bind() 在上面的分析中,将传入addEventCaptureListenerWithPassiveFlagaddEventCaptureListeneraddEventBubbleListenerWithPassiveFlagaddEventBubbleListener进行原生事件的绑定

本质都是调用 dispatchEvent,下面的小节将展开详细分析

ts
function createEventListenerWrapperWithPriority() {
    var eventPriority = getEventPriority(domEventName);
    var listenerWrapper;
    switch (eventPriority) {
        case DiscreteEventPriority:
            listenerWrapper = dispatchDiscreteEvent;
            break;

        case ContinuousEventPriority:
            listenerWrapper = dispatchContinuousEvent;
            break;

        case DefaultEventPriority:
        default:
            listenerWrapper = dispatchEvent;
            break;
    }
    return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}

而从下面的代码可以知道,dispatchDiscreteEventdispatchContinuousEvent 无非就是设置下 setCurrentUpdatePriority()当前的update优先级,然后触发dispatchEvent()

ts
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
    var previousPriority = getCurrentUpdatePriority();
    var prevTransition = ReactCurrentBatchConfig.transition;
    ReactCurrentBatchConfig.transition = null;
    try {
        setCurrentUpdatePriority(DiscreteEventPriority);
        dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
    } finally {
        setCurrentUpdatePriority(previousPriority);
        ReactCurrentBatchConfig.transition = prevTransition;
    }
}
function dispatchContinuousEvent(domEventName, eventSystemFlags, container, nativeEvent) {
  var previousPriority = getCurrentUpdatePriority();
  var prevTransition = ReactCurrentBatchConfig.transition;
  ReactCurrentBatchConfig.transition = null;
  try {
    setCurrentUpdatePriority(ContinuousEventPriority);
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig.transition = prevTransition;
  }
}

因此我们的重点将放在dispatchEvent()的分析上

dispatchEvent

本质触发dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay()

ts
function dispatchEvent() {
  if (!_enabled) {
    return;
  }
  dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent
  );
}

dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay()先触发findInstanceBlockingEvent()检测当前事件是否会被堵塞

如果没有被堵塞,则触发dispatchEventForPluginEventSystem()将事件分发到各个插件系统,然后触发clearIfContinuousEvent()终止未完成的连续事件处理任务

ts
function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent
) {
  var blockedOn = findInstanceBlockingEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent
  );

  if (blockedOn === null) {
    dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      return_targetInst,
      targetContainer
    );
    clearIfContinuousEvent(domEventName, nativeEvent);
    return;
  }

  //...处理SSR和SuspenseComponent相关逻辑
}

findInstanceBlockingEvent()

从下面代码可以看出,主要还是

  • getEventTarget(): 通过原生事件找到对应的DOM元素
  • getClosestInstanceFromNode(): 通过原生DOM拿到fiber,如果获取不到,则不断向上寻找最近一个fiber
  • getNearestMountedFiber(): 当前fiber是不是可能已经不再渲染了?通过getNearestMountedFiber()检测当前fiber是否已经还在 Fiber Tree 上面,否则继续向上寻找fiber

最终将找到的fiber赋值给 return_targetInst,然后返回 null

那么 getEventTarget()getClosestInstanceFromNode()getNearestMountedFiber()到底是如何执行的呢?我们接下来将对这几个方法展开具体分析

ts
function findInstanceBlockingEvent(
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent
) {
  return_targetInst = null;
  var nativeEventTarget = getEventTarget(nativeEvent);
  var targetInst = getClosestInstanceFromNode(nativeEventTarget);

  if (targetInst !== null) {
    var nearestMounted = getNearestMountedFiber(targetInst);

    if (nearestMounted === null) {
      // This tree has been unmounted already. Dispatch without a target.
      targetInst = null;
    } else {
      var tag = nearestMounted.tag;

      if (tag === SuspenseComponent) {
        //...
      } else if (tag === HostRoot) {
        var root = nearestMounted.stateNode;
        if (isRootDehydrated(root)) {
          // If this happens during a replay something went wrong and it might block
          // the whole system.
          return getContainerFromFiber(nearestMounted);
        }
        targetInst = null;
      } else if (nearestMounted !== targetInst) {
        targetInst = null;
      }
    }
  }

  return_targetInst = targetInst;
  return null;
}

getEventTarget

根据原生事件去获取原生DOM数据

ts
function getEventTarget(nativeEvent) {
  // Fallback to nativeEvent.srcElement for IE9
  // https://github.com/facebook/react/issues/12506
  var target = nativeEvent.target || nativeEvent.srcElement || window; // Normalize SVG <use> element events #4963

  if (target.correspondingUseElement) {
    target = target.correspondingUseElement;
  } // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
  // @see http://www.quirksmode.org/js/events_properties.html

  return target.nodeType === TEXT_NODE ? target.parentNode : target;
}

getClosestInstanceFromNode

使用DOM获取对应的 fiber,而targetNode[internalInstanceKey] 是在 completeWork()HostComponent根据fiber去构建DOM时进行映射

如果能够正确拿到对应的 fiber,直接返回 targetInst

如果不存在对应的 fiber,则需要不断向上 targetNode.parentNode 寻找,直到找到最近的DOM对应的fiber

ts
function getClosestInstanceFromNode(targetNode) {
  var targetInst = targetNode[internalInstanceKey];
  if (targetInst) {
    return targetInst;
  }
  var parentNode = targetNode.parentNode;

  while (parentNode) {
    targetInst =
      parentNode[internalContainerInstanceKey] ||
      parentNode[internalInstanceKey];
    if (targetInst) {
      var alternate = targetInst.alternate;
      if (
        targetInst.child !== null ||
        (alternate !== null && alternate.child !== null)
      ) {
        var suspenseInstance = getParentSuspenseInstance(targetNode);
        while (suspenseInstance !== null) {
          //...
        }
      }
      return targetInst;
    }
    targetNode = parentNode;
    parentNode = targetNode.parentNode;
  }
  return null;
}

getNearestMountedFiber

传入对应的 fiber,判断当前 fiber.alternate 是否存在,如果不存在说明该 fiber 已经被卸载,那么就需要重新 node.return寻找下一个不具备 Placement 的 fiber数据

确定完成 fiber 后,使用 node.return 找到最顶端的 fiber,检测是否是 HostRoot,如果是的话,返回上面找到的 fiber 数据;如果不是,则返回 null

ts
function getNearestMountedFiber(fiber) {
  var node = fiber;
  var nearestMounted = fiber;
  if (!fiber.alternate) {
    var nextNode = node;
    do {
      node = nextNode;
      if ((node.flags & (Placement | Hydrating)) !== NoFlags) {
        nearestMounted = node.return;
      }
      nextNode = node.return;
    } while (nextNode);
  } else {
    while (node.return) {
      node = node.return;
    }
  }

  if (node.tag === HostRoot) {
    return nearestMounted;
  }
  return null;
}

dispatchEventForPluginEventSystem事件派发到插件系统

检测当前 fiber (不断fiber=fiber.return)找到的HostRoot对应的DOM跟目前传入的targetContainerNode是否不同,不同则需要重新寻找

注:targetContainerNodecreateContainer()时传入的#root DOM

ts
function dispatchEventForPluginEventSystem() {
  var ancestorInst = targetInst;
  if (
    (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
    (eventSystemFlags & IS_NON_DELEGATED) === 0
  ) {
    var targetContainerNode = targetContainer;
    if (targetInst !== null) {
      var node = targetInst;
      mainLoop: while (true) {
        if (node === null) {
          return;
        }
        var nodeTag = node.tag;
        if (nodeTag === HostRoot || nodeTag === HostPortal) {
          var container = node.stateNode.containerInfo;
          if (isMatchingRootContainer(container, targetContainerNode)) {
            break;
          }
          if (nodeTag === HostPortal) {
            //...
          }
          while (container !== null) {
            var parentNode = getClosestInstanceFromNode(container);
            if (parentNode === null) {
              return;
            }
            var parentTag = parentNode.tag;
            if (parentTag === HostComponent || parentTag === HostText) {
              node = ancestorInst = parentNode;
              continue mainLoop;
            }
            container = container.parentNode;
          }
        }
        node = node.return;
      }
    }
  }

  batchedUpdates(function () {
    return dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst
    );
  });
}

dispatchEventsForPlugins 中,根据事件拿到对应的 DOM 元素,然后触发 -extractEvents() 将对应的回调函数加入到dispatchQueue数组中 -processDispatchQueue() 处理 dispatchQueue 数组,不断遍历执行对应的回调函数 listener

ts
function dispatchEventsForPlugins() {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue = [];
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer
  );
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

extractEvents()

在这个方法中,会触发不同类型事件的extractEvents()事件

ts
function extractEvents() {
  SimpleEventPlugin.extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer
  );
  const shouldProcessPolyfillPlugins =
    (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
  if (shouldProcessPolyfillPlugins) {
    EnterLeaveEventPlugin.extractEvents(
      dispatchQueue,
      domEventName,
      targetInst,
      nativeEvent,
      nativeEventTarget,
      eventSystemFlags,
      targetContainer
    );
    //...
  }
}

SimpleEventPlugin.extractEvents:

React 一开始就会自动执行 registerSimpleEvents() 方法,进行 多个原生事件 -> React事件名称 的绑定,存放在 topLevelEventsToReactNames

ts
function registerSimpleEvents() {
  for (var i = 0; i < simpleEventPluginEvents.length; i++) {
    var eventName = simpleEventPluginEvents[i];
    var domEventName = eventName.toLowerCase();
    var capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1);
    registerSimpleEvent(domEventName, "on" + capitalizedEvent);
  }

  registerSimpleEvent(ANIMATION_END, "onAnimationEnd");
  registerSimpleEvent(ANIMATION_ITERATION, "onAnimationIteration");
  registerSimpleEvent(ANIMATION_START, "onAnimationStart");
  registerSimpleEvent("dblclick", "onDoubleClick");
  registerSimpleEvent("focusin", "onFocus");
  registerSimpleEvent("focusout", "onBlur");
  registerSimpleEvent(TRANSITION_END, "onTransitionEnd");
}
var topLevelEventsToReactNames = new Map(); // NOTE: Capitalization is important in this list!
function registerSimpleEvent(domEventName, reactName) {
  topLevelEventsToReactNames.set(domEventName, reactName);
  registerTwoPhaseEvent(reactName, [domEventName]);
}

执行 SimpleEventPlugin.extractEvents()

  1. 先从 topLevelEventsToReactNames 中利用原生的DOM事件名称获取React事件名称
  2. 触发accumulateSinglePhaseListeners()传入 fiber + React事件名称(比如onClick),通过fiber.props上的属性,获取对应的监听方法
  3. 创建合成事件对象_event = new SyntheticEventCtor()
  4. 将合成事件对象 + fiber.props 获取到的回调函数数组加入到 dispatchQueue
ts
// SimpleEventPlugin.extractEvents
function extractEvents$4() {
  var reactName = topLevelEventsToReactNames.get(domEventName);
  if (reactName === undefined) {
    return;
  }
  var SyntheticEventCtor = SyntheticEvent;
  var reactEventType = domEventName;
  switch (domEventName) {
    case "keydown":
    case "keyup":
      SyntheticEventCtor = SyntheticKeyboardEvent;
      break;
    case "focusin":
      reactEventType = "focus";
      SyntheticEventCtor = SyntheticFocusEvent;
      break;
    //...
  }

  var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  var accumulateTargetOnly = !inCapturePhase && domEventName === "scroll";
  var _listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly
  );
  if (_listeners.length > 0) {
    var _event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget
    );
    dispatchQueue.push({
      event: _event,
      listeners: _listeners,
    });
  }
}

accumulateSinglePhaseListeners()

根据传入的reactName(比如 onClick),使用getListener()props上获取对应的回调方法

然后从当前传入的 fiber 向上遍历 instance = instance.return,如果都是HostComponent都创建对应的回调方法

比如冒泡阶段,当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序

因此从当前fiber => 当前fiber的祖先 都应该触发对应的方法,比如点击某一个 button 的 onClick 的事件,那么 当前fiber + 当前fiber的父亲...当前fiber的祖先 都触发 onClick 事件

ts
function accumulateSinglePhaseListeners(reactName, ...) {
  var captureName = reactName !== null ? reactName + "Capture" : null;
  var reactEventName = inCapturePhase ? captureName : reactName;
  var listeners = [];
  var instance = targetFiber;
  var lastHostComponent = null;
  while (instance !== null) {
    var _instance2 = instance,
      stateNode = _instance2.stateNode,
      tag = _instance2.tag;

    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;
      if (reactEventName !== null) {
        var listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent)
          );
        }
      }
    }
    if (accumulateTargetOnly) {
      break;
    }
    instance = instance.return;
  }
  return listeners;
}
ts
function getListener(inst, registrationName) {
  var stateNode = inst.stateNode;
  if (stateNode === null) {
    return null;
  }
  var props = getFiberCurrentPropsFromNode(stateNode);
  if (props === null) {
    return null;
  }
  var listener = props[registrationName];
  if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
    return null;
  }
  return listener;
}
function createDispatchListener(instance, listener, currentTarget) {
  return {
    instance: instance,
    listener: listener,
    currentTarget: currentTarget,
  };
}

processDispatchQueue()

拿出dispatchQueue队列中的数据

  • 如果是inCapturePhase捕获阶段,则从dispatchListeners.length - 1开始遍历
  • 如果是冒泡阶段,则从0开始遍历

冒泡: 当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序

捕获:

  • 捕获阶段(Capturing phase)—— 事件(从 Window)向下走近元素。
  • 目标阶段(Target phase)—— 事件到达目标元素。
  • 冒泡阶段(Bubbling phase)—— 事件从元素上开始冒泡
ts
function processDispatchQueue(dispatchQueue, eventSystemFlags) {
  var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (var i = 0; i < dispatchQueue.length; i++) {
    var _dispatchQueue$i = dispatchQueue[i],
      event = _dispatchQueue$i.event,
      listeners = _dispatchQueue$i.listeners;
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
}
function processDispatchQueueItemsInOrder() {
  var previousInstance;
  if (inCapturePhase) {
    for (var i = dispatchListeners.length - 1; i >= 0; i--) {
      var _dispatchListeners$i = dispatchListeners[i],
        instance = _dispatchListeners$i.instance,
        currentTarget = _dispatchListeners$i.currentTarget,
        listener = _dispatchListeners$i.listener;
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
    for (var _i = 0; _i < dispatchListeners.length; _i++) {
      //...跟上面代码逻辑一致
    }
  }
}

executeDispatch():

核心代码就是执行传入的listener方法

ts
function executeDispatch(event, listener, currentTarget) {
    var type = event.type || "unknown-event";
    event.currentTarget = currentTarget;
    invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
    event.currentTarget = null;
}
function invokeGuardedCallbackAndCatchFirstError(name, func, ...) {
  invokeGuardedCallback.apply(this, arguments);
}
var reporter = {
  onError: function (error) {
    hasError = true;
    caughtError = error;
  },
};
function invokeGuardedCallback(name, func, ...) {
  invokeGuardedCallbackImpl.apply(reporter, arguments);
}
var invokeGuardedCallbackImpl = invokeGuardedCallbackProd;
function invokeGuardedCallbackProd(name, func, ...) {
  var funcArgs = Array.prototype.slice.call(arguments, 3);
  try {
    func.apply(context, funcArgs);
  } catch (error) {
    this.onError(error);
  }
}

clearIfContinuousEvent

终止未完成的连续事件处理任务,确保高频事件场景下的性能优化和资源安全

ts
function clearIfContinuousEvent(domEventName, nativeEvent) {
  switch (domEventName) {
    case "focusin":
    case "focusout":
      queuedFocus = null;
      break;
    case "dragenter":
    case "dragleave":
      queuedDrag = null;
      break;
    case "mouseover":
    case "mouseout":
      queuedMouse = null;
      break;
    case "pointerover":
    case "pointerout": {
      var pointerId = nativeEvent.pointerId;
      queuedPointers.delete(pointerId);
      break;
    }
    case "gotpointercapture":
    case "lostpointercapture": {
      var _pointerId = nativeEvent.pointerId;
      queuedPointerCaptures.delete(_pointerId);
      break;
    }
  }
}