Chrome 浏览器的更新导致 jQuery 反复发版,只因 :has 这个伪类

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 最近 jQuery 似乎又“回光返照”了一下,窜稀式地连更了几个小版本。

最近 jQuery 似乎又“回光返照”了一下,窜稀式地连更了几个小版本。

我仔细看了看,并没有新功能出现,不过还挺有意思的,也让我学习到了新的东西。简单来说,这几个版本的更新主要与 Chrome 引入新选择器产生的缺陷有关。那么谷歌浏览器是如何导致 jQuery 产生 Bug 的,又是为何在修复之后又引发了新 Bug 的,这还得从 :has 伪类说起。

关于 :has() 伪类

CSS 在很早前就存在子代和后代选择器,而父元素的选择器出于性能的考虑却迟迟没有浏览器能支持,虽然规范早已存在,但是真正支持它却是不久前才发生的事。

这个伪类通过把可容错相对选择器列表作为参数,提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法,例如:

h1:has(+ p) { color: red; }

这段 css 表示的是:选择一个 h1 标签,当它有一个相邻节点是 p 标签时,这个 h1 标签文字会显示为红色,蛮好理解的对吧。

该伪类在 2022 年由 Safari 浏览器首先开始支持,随后 Chrome 105 也启用了这个原生的 :has 伪类,那么这个新选择器的支持为什么会给 jQuery 带来 Bug 呢?

都是 Chrome 浏览器的锅

其实 jQuery 长期以来一直支持 :has 伪类,甚至扩展了 :contains 这样的 api 出现在选择器中,它是以错误抛出(try-catch)的形式来判断应该使用浏览器的 querySelectorAll 还是 Sizzle(jQuery 开源的一套 css 解析器)来匹配样式的。

例如 :has(:contains("Item")) 这种形式的选择器,在以前会视为错误,所以当触发异常抛出时 jQuery 就会使用自己的解析工具从而保证选择器能正常工作。

但是现在 Chrome 更新了浏览器的 :has 原生支持,且作为参数的是可容错相对选择器列表,于是上述这种形式的写法直接被忽略,并不会报错,按照 jQuery 原本的处理方式,这里不抛出异常的话就会直接使用原生选择器,而原生选择器接收到错误的参数又直接“罢工”,所以导致了 jQuery 所有可追溯到最早版本的 :has 选择器都被破坏了。

这里 jQuery 团队用了一个梗作为段落标题:“打破互联网的并非拉尔夫”(" It wasn’t Ralph that broke the internet." ——拉尔夫是迪士尼电影《无敌破坏王2:大闹互联网》中的主角)我的理解是在说凡事都有两面性,新事物也许是把双刃剑的意思吧,属于是委婉表达 :has 是个好东西但 Chrome 处理得有问题,虽然 jQuery 出了 Bug 但宝宝真的冤啊!放在咱们中文互联网的语境大概就是 jQuery 对 Chrome 唱起了:

不过 Chrome 团队也很快实施了一种解决方法来修复以前的 jQuery 版本,而 Safari 因为对 :has() 的实现的处理方式略有不同,没有遇到同样的问题。(Chrome:好了别说了,要不你再重启一下电脑试试?应该就没问题了😋)

“允许的”并不代表就是“正确的”

上文提到了 :has 包含的参数是可容错的,遵循所谓“宽容解析”( forgiving parsing )原则,怎么理解呢?我们先来看看什么是“无效的选择器列表”,如下有一段 CSS 规则集:

h1 {
  color: red;
}
h2:invalid{
  color: red;
}
h3 {
  color: red;
}

很明显,上面 :invalid 是错误的一个伪类,所以 h2 的那段样式将是无效的,但通常我们还会这么写:

h1, h2:invalid, h3 {
  color: red;
}

请注意,这两段规则集只在完全正确时是等效的,而下面这段规则集里只要存在一个不正确,整个规则都将不会被解析,也就是说 h1h2 不会有样式被应用了。

聪明的你应该想到了,可容错选择器就是在这个背景下诞生的,以 :is 为例,将第二段规则集改为如下写法,就与第一段完全等效了:

:is(h1, h2:invalid, h3) {
  color: red;
}

:is:where:has 这些伪类都是相似的,因为参数列表可容错,所以它允许你传入可能有错误或不支持的参数,并且不会因为错误而“中断”选择器。

修复 Bug 往往会引发另一个 Bug

起初 jQuery 团队想到的修补方案,是通过判断包含了 :has 的选择器则强制使用 jQuery 的选择器引擎来解析,但这并不灵活,在社区的讨论声中大家普遍认为 jQuery 不应该依赖 try-catch,于是在 3.6.2 中 jQuery 开始改用 native 的策略 CSS.supports 来确定选择器是直接传递给 querySelectorAll 还是通过 jQuery 的选择器引擎。打住,这又是什么我没见过的黑魔法,吓得我赶紧翻了翻 MDN 文档。

CSS.supports() 有两种不同的传值形式。

第一种用来检验浏览器对于一对“属性 - 属性值”的支持,例如:

CSS.supports("display", "flex");
CSS.supports('--foo', 'red');

另一种传入一个包含检测条件的字符串:DOMString,例如:

CSS.supports('(--foo: red)');
CSS.supports("( transform-origin: 5% 5% )");

这种方法是支持选择器作为字符串来检测的,例如:

CSS.supports("selector(span)") // true

这样就可以检测特定的伪类规则是否在浏览器中支持啦:

通过这个 supports("selector(SELECTOR)") 来确定一个选择器直接传递给 querySelectorAll 是否有效,返回 false 则退回到到自己的选择器引擎(Sizzle),这似乎是个理想的方案,不过 jQuery 团队明显对这个静态方法并不熟悉,所以在实现上犯了一点小错误,因为 SELECTOR 只支持 <complex-selector> 而不能是 <complex-selector-list>,例如:

CSS.supports("selector(div)"); // true
CSS.supports("selector(div, span)"); // false

这意味着所有复杂的选择器列表都通过 Sizzle 解析而不是 querySelectorAll,所以
jQuery 又发了一个新版本 3.6.3 来修复这一问题。

但是,事情还没完,很快 jQuery 又发现这种实现方式仍然是有缺陷的😅

有某些选择器在过去工作得很好,但 CSS.supports 却好像并不认为它们可以正常工作,例如原生的 querySelectorAll.("[attr=val") 是可以正常使用的,但无论是 CSS.supports( "selector(:is([attr=val))" ) 还是 CSS.supports( "selector([attr=val)" ) 都是返回 false.

我在当前浏览器 Chrome 110 中测试结果和官方不太一致,看来谷歌又偷偷改了点东西,根据 jQuery 官方的说法只有 Firefox 是相对稳定的。

在收到多条 issues 之后,jQuery 考虑恢复到以前的方式,同时,在 jQuery 与规范编写者和供应商的讨论中,一致认为需要防止类似 :has 的问题在未来再次发生,虽说收到一些“保证”,但考虑浏览器还需要一段时间去逐渐更新,所以在 3 月又发布了 3.6.4 这个版本并建议用户升级。

以上就是文章的全部内容,感谢看到这里!以上就是 jQuery 最近更新几个版本的曲折历程了,看完你觉得有收获吗?是不是对浏览器和选择器有了新的认识呢?本人知识水平有限,如有错误望不吝指正,如果觉得写得不错,对你有所帮助或启发,可以点赞收藏支持一下,也欢迎关注,我会更新更多实用的前端知识与技巧。我是 茶无味de一天(公众号: 品味前端),希望与你共同成长~

相关资料

jQuery 3.6.2 Released!

jQuery 3.6.3 Released: A Quick Selector Fix

jQuery 3.6.4 Released: Selector Forgiveness

MDN - CSS.supports()MDN - :has

Issue 1358953: :has pseudo-class breaks jQuery custom selectors

相关文章
|
3月前
|
Web App开发 数据采集 存储
WebDriver与Chrome DevTools Protocol:如何在浏览器自动化中提升效率
本文探讨了如何利用Chrome DevTools Protocol (CDP) 与 Selenium WebDriver 提升浏览器自动化效率,结合代理IP技术高效采集微博数据。通过CDP,开发者可直接操作浏览器底层功能,如网络拦截、性能分析等,增强控制精度。示例代码展示了如何设置代理IP、cookie及user-agent来模拟真实用户行为,提高数据抓取成功率与稳定性。适用于需要频繁抓取互联网数据的应用场景。
538 3
WebDriver与Chrome DevTools Protocol:如何在浏览器自动化中提升效率
|
1月前
|
Web App开发 缓存 安全
WIN11 Chrome 双击打不开闪退及Chrome浏览器不能拖拽文件crx
【11月更文挑战第6天】本文介绍了 WIN11 系统中 Chrome 浏览器双击打不开闪退及不能拖拽文件 crx 的原因和解决方法。包括浏览器版本过旧、扩展程序冲突、硬件加速问题、缓存过多、安全软件冲突、系统文件损坏、用户配置文件损坏等问题的解决方案,以及 crx 文件的屏蔽、权限问题和文件格式问题的处理方法。
105 2
|
1月前
|
Web App开发 Linux iOS开发
Chrome浏览器如何导出所有书签并导入书签
【11月更文挑战第4天】本文介绍了如何在 Chrome 浏览器中导出和导入书签。导出时,打开书签管理器,点击“整理”按钮选择“导出书签”,保存为 HTML 文件。导入时,同样打开书签管理器,点击“整理”按钮选择“导入书签”,选择之前导出的 HTML 文件即可。其他主流浏览器也支持导入这种格式的书签文件。
152 2
|
1月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
246 9
|
2月前
|
Web App开发 开发者
|
2月前
|
Web App开发 JSON 安全
Chrome浏览器的跨域问题
【10月更文挑战第6天】
|
2月前
|
Web App开发 缓存 安全
Chrome浏览器启动参数大全
这是一组用于定制浏览器行为的命令行参数,包括但不限于:不停用过期插件、放行非安全内容、允许应用中心脚本、停用GPU加速视频、禁用桌面通知、禁用拓展及各类API、调整缓存设置、启用打印预览、隐身模式启动、设定语言、使用代理服务器、无头模式运行等。通过这些参数,用户可以根据需求灵活调整浏览器功能与性能。
|
3月前
|
Web App开发 存储 前端开发
Chrome浏览器的跨域问题
Chrome浏览器的跨域问题
|
4月前
|
Web App开发
Chrome——谷歌浏览器chrome如何模拟其他客户端
Chrome——谷歌浏览器chrome如何模拟其他客户端
110 1
Chrome——谷歌浏览器chrome如何模拟其他客户端
|
4月前
|
Web App开发 JSON 安全
【跨域难题终结者】:一键解锁Chrome浏览器神秘设置,彻底告别开发阶段的跨域烦恼!
【8月更文挑战第20天】跨域是前端开发常遇难题,尤其在前后端分离项目中。浏览器因安全考量会阻止不同源间的请求。本文对比CORS、JSONP、代理服务器等解法,并介绍开发阶段通过调整Chrome设置来临时禁用跨域限制的方法,提供启动Chrome及使用`fetch`API示例,适合快速测试。但请注意这不适用于生产环境,存在一定安全风险。
1127 1