跳到主要内容

· 阅读需 5 分钟

问题

前两天在掘金大模型子站遇到一个问题,使用 $0.click() 模拟点击弹窗的关闭按钮时,出现报错 click is not a function 且弹窗没有关闭,原因是关闭按钮使用的 svg 元素没有 click 方法。

随后使用 $0.dispatchEvent(new MouseEvent('click')),代码没报错但是弹窗没有关闭。

模拟点击失败截图

解决

经过调试分析发现,将 new MouseEvent('click') 的第二个参数设置为 {bubbles: true} 就能关闭弹窗,成功代码: $0.dispatchEvent(new MouseEvent('click',{bubbles: true}))

原因

  • 1、HTMLElement.click() 失败原因:因为 svg 元素没有 click 函数
  • 2、EventTarget.dispatchEvent(new MouseEvent('click')) 失败原因:掘金大模型子站使用 React 编写,模拟点击事件没有冒泡,未能触发 React onClick 事件
    • React 的合成事件区分捕获与冒泡,onClick 只在冒泡阶段被触发,捕获阶段不会触发
    • EventTarget.dispatchEvent(new MouseEvent('click')) 分发的事件没有冒泡阶段,所以没有触发 React onClick 事件
    • 代码修改为 EventTarget.dispatchEvent(new MouseEvent('click',{bubbles:true})),分发的事件既有捕获阶段又有冒泡阶段,可以触发 React onClick 事件

分析要点

搭建最简复现环境

为了解决调试不便和影响因素过多等问题,在 codesandbox 搭建了一个最简复现环境,关键代码如下:

// App.js
export default function App() {
function clickHandler() {
console.log("onClick");
}

return (
<div className="App" onClick={clickHandler}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}

对比分析

在上述环境中,HTMLElement.click() 可以正常调用 clickHandler 函数,而 EventTarget.dispatchEvent(new MouseEvent('click')) 没有调用 clickHandler 函数。

click event

如果嗅觉足够灵敏,再搭建一个类似的原生 JavaScript 复现环境,很快就能发现前面这两种方式都能调用 click 事件绑定的函数,只是事件对象属性略有不同,也能很快发现问题出在 new MouseEvent 的第二个参数上。

分析调用栈

为了排查失败的真正原因,可以在 clickHandler 函数中添加断点,对调用栈中的函数逐个排查。

debugger断点截图

关键源码分析

事件队列不同

经过排查,在 React 代码 react-dom.development.js 文件中的 dispatchEventsForPlugins 函数发现了端倪:

  • 使用 $0.click() 触发点击事件,该函数被执行两次
    • 第一次函数中的变量 dispatchQueue 为空数组,不会调用 clickHandler 函数
    • 第二次函数中的变量 dispatchQueue 不为空,会调用 clickHandler 函数
  • 使用 $0.dispatchEvent(new MouseEvent('click')) 触发点击事件,该函数被执行一次,函数中的 dispatchQueue 为空数组,不会调用 clickHandler 函数

dispatchQueue断点截图

获取监听器的名称与结果不同

从上图可以看到 dispatchQueue[0].listeners[0].listener 的值就是 App.js 中的 clickHandler 函数。这里的 dispatchQueue 可以简单理解为即将执行的事件监听器组成的队列,队列不为空时其中的监听器会逐一执行。

接下来的关键点是搞清楚队列 dispatchQueue 如何被填充,排查发现是被 extractEvents$5 函数所修改与填充,队列中元素的 listener 是通过 getListener 函数获得:

  • 使用 $0.click() 触发点击事件,getListener 函数被执行两次
    • 第一次函数的入参 registrationName 值为 onClickCapture,返回结果为 undefined
    • 第二次函数的入参 registrationName 值为 onClick,返回结果为 App.js 中的 clickHandler 函数
  • 使用 $0.dispatchEvent(new MouseEvent('click')) 触发点击事件,getListener 函数被执行一次,函数的入参 registrationName 值为 onClickCapture,返回结果为 undefined

getListener断点截图

通过以上分析可以推断出问题的根本原因。

总结

  • 模拟点击事件常用两种方式:HTMLElement.click()EventTarget.dispatchEvent()
  • 使用 EventTarget.dispatchEvent() 时,记得给事件加上冒泡避免踩坑
  • React 的合成事件区分捕获与冒泡,onClickCapture 在捕获阶段触发,onClick 在冒泡阶段触发,其他事件类似

EventTarget.dispatchEvent(new MouseEvent('click'))VueAngular 中可以正常调用

相关资料

· 阅读需 13 分钟

1、背景

这两年我所在的技术团队受到教育和互联网双重 Debuff 加持,团队人数从 300+减少到 20+,期间接手了很多前端项目,也遇到很多问题。比如有些项目本地启动速度很慢,有些项目配置复杂、过度封装,一系列问题使得开发体验很差。

为了提升开发体验,我在项目中引入 Vite 用于开发调试(不用于构建)。配置好之后启动项目,页面白屏了,浏览器控制台输出这个错误:Module "crypto" has been externalized for browser compatibility

image.png

· 阅读需 9 分钟

1、问题描述

前几天又遇到个神奇Bug,公司某系统的页面出现点击按钮没有反应的问题,反馈问题的用户能稳定复现,技术团队无法复现。

浏览器为 Chrome 115,控制台可见大量 iframe跨域 报错,具体情况如下: image.png

· 阅读需 6 分钟

经常在网上阅读文档、博客、论文的朋友可能会遇到这样的问题:网页没有目录,为了找到想要的信息需要反复滚动页面甚至看完整篇文章。这不仅浪费时间,还影响阅读体验。

1、简介

OneToc 是一个浏览器插件,它的主要用途是生成网页内容大纲,提供结构清晰的目录和便捷的导航以提高浏览和定位信息的效率,让阅读变得轻松一点,在阅读长文的场景下效果更佳。

· 阅读需 9 分钟

1、问题描述

最近接手的几个项目用的包管理器是 yarn@v1.22.19,在安装依赖后无论是否成功,总是出现网络连接问题而且会卡很长时间,然后会出现几行这样的异常日志:info There appears to be trouble with your network connection. Retrying...

image.png

有时一些神奇的包(比如 node-sass)出现异常会导致安装失败,结果卡了半天才发现失败,真的让人很崩溃。另外在yarngithub 仓库中有数十条相关的 issue,时间跨度从2016年到2022年足足6年,原因和方案众说纷纭。我很好奇这到底是个什么神奇的问题居然6年都没解决,因此决定一探究竟。

· 阅读需 8 分钟

1、背景

以前我认为,只要开启代理,本地所有请求都会先经过代理再访问网络资源。直到这两天,用 Node.js 访问 Google 出现报错。

代码长这样:

const chalk = require('chalk');
const axios = require('axios');

const url = 'https://www.google.com';

axios
.get(url)
.then((a) => {
console.log(chalk.green(`Request ${url} finish, status: ${a.status}.`));
})
.catch((e) => {
console.log(chalk.red(`Request ${url} error!\nMessage: ${e.message}.`));
});