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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
云解析DNS,个人版 1个月
简介: 最近 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开发
在 HTML 中禁用 Chrome 浏览器的 Google 翻译功能
在 html 标签中添加 translate=“no” 属性,浏览器将不会翻译整个页面。
204 0
|
3月前
|
Web App开发 JavaScript 前端开发
从零开始,轻松打造个人化Chrome浏览器插件
从零开始,轻松打造个人化Chrome浏览器插件
130 0
|
6天前
|
Web App开发
Chrome——谷歌浏览器chrome如何模拟其他客户端
Chrome——谷歌浏览器chrome如何模拟其他客户端
23 1
Chrome——谷歌浏览器chrome如何模拟其他客户端
|
2月前
|
Web App开发 前端开发 JavaScript
Chrome 浏览器中执行 JavaScript
Chrome 浏览器中执行 JavaScript
120 0
|
10天前
|
Web App开发 前端开发 JavaScript
手摸手教你,从0到1开发一个Chrome浏览器插件
开发 Chrome 插件既有趣又具成就感。本教程将引导你从零开始,逐步创建一个简单的 Chrome 插件。首先了解 Chrome 插件是可增强浏览器功能的小程序。以一个基础示例开始,你将学习如何设置开发环境,包括安装 Chrome 和准备文本编辑器,并掌握 HTML、CSS 和 JavaScript 的基础知识。接着,我们将构建插件的基本结构,涉及 `manifest.json` 配置文件、`background.js` 后台脚本、`popup.html` 用户界面以及 `style.css` 样式表。
55 8
|
4天前
|
Web App开发
Chrome浏览器导出HTTPS证书
Chrome浏览器导出HTTPS证书
9 0
Chrome浏览器导出HTTPS证书
|
28天前
|
Web App开发 JavaScript 前端开发
Chrome插件实现问题之最新的 Chrome 浏览器架构有什么新的改变吗
Chrome插件实现问题之最新的 Chrome 浏览器架构有什么新的改变吗
|
28天前
|
JavaScript 前端开发 Web App开发
Chrome插件实现问题之单进程浏览器的不稳定主要体现在什么地方
Chrome插件实现问题之单进程浏览器的不稳定主要体现在什么地方
|
28天前
|
Web App开发 前端开发 JavaScript
Chrome插件实现问题之用户在浏览器中输入URL后,浏览器进程会进行什么操作
Chrome插件实现问题之用户在浏览器中输入URL后,浏览器进程会进行什么操作
|
1月前
|
Web App开发
软件开发常见流程之移动端调试方法,利用Chrome(谷歌浏览器)的模拟手机调试,搭建本地Web服务器,手机和服务器在一个局域网,通过手机访问服务器,使用服务器,利用ip实现域名访问
软件开发常见流程之移动端调试方法,利用Chrome(谷歌浏览器)的模拟手机调试,搭建本地Web服务器,手机和服务器在一个局域网,通过手机访问服务器,使用服务器,利用ip实现域名访问

相关课程

更多