跳到主要内容

· 阅读需 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')) 在 Vue 和 Angular 中可以正常调用

相关资料​

· 阅读需 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)出现异常会导致安装失败,结果卡了半天才发现失败,真的让人很崩溃。另外在yarn 的 github 仓库中有数十条相关的 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}.`));
});