jQuery 4.0.0

这是近十年来的首个重大版本更新,包含若干破坏性变更,升级前请务必仔细阅读以下说明。

2006年1月14日,John Resig在纽约市的BarCamp大会上首次发布了名为jQuery的JavaScript库。二十年后的今天,jQuery团队欣然宣布正式发布jQuery 4.0.0版本。历经漫长的开发周期和多次预发布版本,jQuery 4.0.0带来了诸多改进与现代化升级。这是近十年来的首个重大版本更新,包含若干破坏性变更,升级前请务必仔细阅读以下说明。不过我们预计大多数用户只需对代码进行最小程度的修改即可完成升级。

许多破坏性变更其实是团队多年来想做却无法在补丁或次要版本中实现的。我们精简了遗留代码,移除了部分已废弃的API,删除了公共函数中从未文档化的内部参数,并取消了对某些过于复杂的“魔法”行为的支持。

我们已准备好升级指南jQuery Migrate插件协助过渡。请升级后如遇问题及时反馈

本次更新如常发布于我们的CDN及npm包管理器。其他第三方CDN预计也将陆续提供更新,但请注意我们无法控制其发布时间,需预留相应时间。以下是jQuery 4.0.0的主要更新亮点:

元素周期表

移除IE<11支持

jQuery 4.0不再支持IE 10及更早版本。可能有人会问为何未移除IE 11支持。我们计划分阶段移除支持,下一步将在jQuery 5.0中实现。当前阶段,我们将首先移除针对 IE 11 之前版本的专项支持代码。

同时终止对其他过时浏览器的支持,包括:Edge Legacy、iOS 系统前三个版本、Firefox 前两个版本(Firefox ESR 除外)以及 Android 浏览器。您的代码无需进行任何修改。若需支持上述浏览器,请继续使用 jQuery 3.x 版本。

可信类型与CSP

jQuery 4.0新增对可信类型的支持,确保以可信HTML 作为 jQuery 操作方法的输入时,不会违反 require-trusted-types-for 内容安全策略指令。

此外,虽然部分AJAX请求已使用<script>标签来维护crossdomain等属性,但我们已将大多数异步脚本请求切换为使用<script>标签,以避免因使用内联脚本引发的CSP错误。仍有少数场景使用XHR进行异步脚本请求,例如传递“headers”选项时(请改用scriptAttrs!),但我们现已尽可能统一采用<script>标签。

jQuery源代码迁移至ES模块

main分支上的jQuery源代码从AMD迁移至ES模块时,堪称里程碑时刻。尽管 jQuery 源代码始终随版本发布在 npm 和 GitHub 上,但此前无法直接作为模块导入——必须通过 jQuery 专用的构建工具 RequireJS。如今我们已切换至 Rollup 进行打包,并单独运行所有 ES 模块测试。这使得 jQuery 通过 <script type=module> 标签实现了与现代构建工具、开发流程及浏览器的兼容性。

已废弃 API 的移除

这些函数在多个版本中已被标记为废弃。随着重大版本的发布,现正式移除这些函数。它们要么始终定位为内部功能,要么已在所有支持的浏览器中拥有原生替代方案。移除的函数包括:

jQuery.isArrayjQuery.parseJSONjQuery.trimjQuery.typejQuery.nowjQuery.isNumericjQuery.isFunctionjQuery.isWindowjQuery.camelCasejQuery.nodeNamejQuery.cssNumberjQuery.cssProps 以及 jQuery.fx.interval

请改用原生等效方法,如 Array.isArray()JSON.parse()String.prototype.trim()Date.now()

废弃 API 的移除结合对旧版 IE 的支持代码删除,最终使压缩后文件大小减少了超过 3k 字节。

从 jQuery 原型中移除的内部专用方法

jQuery原型中长期存在着与其他方法行为迥异的数组方法,这些方法始终仅限于内部使用。这些方法包括push、sort和splice。现已从jQuery原型中移除。若您曾使用这些方法,可将$elems.push( elem )替换为[].push.call( $elems, elem )

焦点事件顺序现遵循 W3C 规范

长期以来,浏览器对焦点事件(包括 focusin、focusout、focus 和 blur)的触发顺序存在分歧。如今 jQuery 4.0 支持的所有浏览器最新版本终于统一了事件顺序。遗憾的是,该顺序与 jQuery 多年前确立的规范不符,因此构成破坏性变更。至少现在大家终于达成共识了!

从 jQuery 4.0 开始,我们不再覆盖原生行为。这意味着除 IE 之外的所有浏览器都将遵循当前 W3C 规范:

  1. blur
  2. focusout
  3. focus
  4. focusin

而 jQuery 旧版本的顺序为:focusout, blur, focusin, focus。讽刺的是,唯一曾遵循旧版W3C规范(2023年更新前)的浏览器竟是Internet Explorer。

精简版更新

jQuery 4.0.0通过移除Deferreds和Callbacks进一步缩减了精简版体积(gzip压缩后约19.5k字节!)。Deferreds 长期支持 Promises A+ 标准,因此在多数情况下可改用原生 Promise,且除 IE11 外所有 jQuery 支持的浏览器均可使用。虽然 Deferreds 具备原生 Promise 不支持的额外功能,但绝大多数用法均可迁移至 Promise 方法。若需支持IE11,建议使用主构建版本或为原生Promises添加polyfill。

下载

您可从jQuery CDN获取文件,或直接链接至:

https://code.jquery.com/jquery-4.0.0.js

https://code.jquery.com/jquery-4.0.0.min.js

也可通过 npm 安装:

npm install jquery@4.0.0

精简版构建

有时您可能不需要ajax功能,或更倾向于使用专注于ajax请求的独立库。此外,通过CSS与类操作组合实现网页动画往往更为简洁。最后,除IE11外,所有jQuery支持的浏览器现已全面原生支持Promises,因此在多数情况下不再需要Deferreds和Callbacks。除包含完整功能的常规版外,我们还发布了剔除这些模块的“精简版”。如今jQuery体积极少成为加载性能问题,但精简版经gzip压缩后比常规版小约8k字节。这些文件同样可在npm包管理器和CDN获取:

https://code.jquery.com/jquery-4.0.0.slim.js

https://code.jquery.com/jquery-4.0.0.slim.min.js

这些更新已在npm和Bower上作为当前版本发布。获取jQuery的所有方式详见https://jquery.com/download/。公共CDN今日已接收更新文件,请等待数日更新完成。若需快速启动,可先使用我们CDN上的文件,待其更新后再行替换。

鸣谢

衷心感谢所有参与本次版本发布的同仁,包括提交补丁、报告漏洞或参与测试的Alex艾哈迈德·S·埃尔-阿菲菲fecore1达拉斯·弗雷泽理查德·吉布森Michał Gołębiowski-OwczarekPierre GrimaudGabriela GutierrezJonathan内克梅廷·卡拉卡亚安德斯·卡塞奥格金元燮西蒙·莱格纳沙尚卡·纳塔拉吉帕特·奥卡拉汉克里斯蒂安·奥利夫迪米特里·帕帕多普洛斯·奥尔法诺斯朴元亨布鲁诺·皮埃尔任宝硕比阿特丽斯·雷泽纳肖恩·罗宾逊埃德·桑德斯蒂莫·蒂霍夫汤姆克里斯蒂安·温茨ygj6 以及整个 jQuery 团队。

jQuery 二十岁生日快乐!

过去二十年间,众多杰出人士为jQuery及其相关项目做出了贡献,我们中的许多人齐聚达拉斯参加了一场重聚活动。John Resig甚至通过Zoom参与其中。本次发布正是在我们共聚一堂时发布的。

图0:jQuery 4.0.0

更新日志

完整更新日志: 4.0.0

Ajax

  • 禁止将数组数据视为二进制数据处理 (992a1911)
  • 允许二进制数据使用 processData: true (ce264e07)
  • 支持二进制数据(包括 FormData)(a7ed9a7b)
  • 支持跨域脚本传输时的 headers 参数 (#5142, 6d136443)
  • jQuery.get 中支持将 null 作为成功函数 (#4989, 74978b7e)
  • 除非提供 dataType,否则不自动执行脚本 (#4822, 025da4dd)
  • 使responseJSON支持错误的同域JSONP请求(68b4ec59)
  • 执行 JSONP 错误脚本响应 (#4771, a1e619b0)
  • 避免异步请求中脚本传输的CSP错误 (#3969, 07a8e4a1)
  • 移除JSON到JSONP的自动升级逻辑(#1799, #3376, e7b3bc48)
  • 若存在 content-type 头部值,则覆盖 s.contentType (#4119, 7fb90a6b)
  • 弃用 AJAX 事件别名,将事件/别名内联到已弃用的部分 (23d53928)
  • 对于失败的HTTP响应不执行脚本 (#4250, 50871a5a)
  • 简化 jQuery.ajaxSettings.xhr (#1967, abdc89ac)

属性

  • 使 .attr( name, false ) 移除所有非 ARIA 属性 (#5388, 063831b6)
  • 削减几个字节 (b40a4807)
  • 设置器中不将属性转为字符串 (#4948, 4250b628)
  • 移除 toggleClass(boolean|undefined) 签名 (#3388, a4421101)
  • 重构 val() 函数:保留换行符,隔离 IE 兼容方案 (ff281991)
  • 除 IE 外完全不设置 type 属性钩子 (9e66fe9a)

CSS

  • 修复表格<col>元素的尺寸问题 (#5628, eca2a564)
  • 在 finalPropName 中移除缓存 (640d5825)
  • 测试:修复测试并支持CSS缩放下的测试 (#5489, 071f6dba)
  • 修复初始隐藏的 iframe 对 reliableTrDimensions 的支持测试 (b1e66a5f)
  • 选择器:与 3.x 版本保持一致,移除外部 selector.js 包装器 (53cf7244)
  • 使 reliableTrDimensions 支持测试兼容 Bootstrap CSS (#5270, 65b85031)
  • 使 offsetHeight( true ) 等方法包含负边距(#3982, bce13b72)
  • 当 CSS 变量值仅含空格时返回 undefined (#5120) (7eb00196)
  • 不对未定义的自定义属性修剪空格 (#5105, ed306c02)
  • addClass( array ) 中跳过假值,压缩代码 (#4998, a338b407)
  • 合理化 CSS 属性值的 rtrim 操作 (655c0ed5)
  • 处理 CSS 自定义属性值周围的空格 (#4926, efadfe99)
  • 在 jQuery 精简构建中加入 showhidetoggle 方法 (297d18dd)
  • 移除不透明度 CSS 钩子 (865469f5)
  • 修复 IE/Edge 浏览器中表格行 getComputedStyle 方法的缺陷 (#4490, 26415e08)
  • 除少数例外情况外,不自动为属性添加“px”单位 (#2795, 00a9c2e5)

核心

  • 移除过时的临时解决方案,更新支持注释 (e2fe97b7)
  • $.parseHTMLdocument.implementation 切换至 DOMParser (0e123509)
  • 修复导出配置,使打包工具兼容 ESM 与 CommonJS (#5416, 60f11b58)
  • 补充命名导出相关说明 (5f869590)
  • 简化代码以减少浏览器支持限制 (93ca49e6)
  • 将工厂移至独立导出文件 (46f6e3da)
  • src/ 中使用命名导出 (#5262, f75daab0)
  • 修复 HTMLDocument 对象上 jQuery.text() 的回归问题 (#5264, a75d6b52)
  • 选择器:将 jQuery.contains 从选择器模块移至核心模块 (024d8719)
  • 移除 jQuery.fn.init 的 root 参数 (d2436df3)
  • 避免依赖输入参数是否存在 splice 方法 (9c6f64c7)
  • 操作:添加基础可信HTML支持 (#4409, de5398a6)
  • 在parseXML中报告浏览器错误 (#4784, 89697325)
  • 使 jQuery.isXMLDoc 接受假值输入 (#4782, fd421097)
  • 停止支持旧版Edge(即非Chromium内核的Microsoft Edge) (#4568, e35fb62d)
  • 在其上下文中执行 iframe 脚本,在 globalEval 中添加文档参数 (#45184592595b)
  • 精简构建时同样排除回调函数与延迟模块 (fbc44f52)
  • 从 AMD 迁移至 ES 模块!🎉 (d0ce00cd)
  • 在支持环境中使用 Array.prototype.flat (#4320, 9df4f1de)
  • 从 jQuery 原型中移除 push、sort 和 splice 的私有副本 (b59107f5)
  • 实现 .even() 与 .odd() 方法以替代 POS :even 与 :odd (78420d42)
  • 弃用 jQuery.trim (#4363, 5ea59460)
  • 移除 IE 专属支持测试,改为依赖 document.documentMode (#4386, 3527a384)
  • 停止支持 IE <11、iOS <11、Firefox <65、Android 浏览器及 PhantomJS (#3950, #4299, cf84696f)
  • 移除已弃用的 jQuery API (#4056, 58f0c00b)

数据

  • 重构以减小体积 (805cdb43)
  • 事件:操作:防止与 Object.prototype 发生冲突 (#3256, 9d76c0b1)
  • 将数据与 css/effects 的驼峰式命名实现分离 (#3355, 8fae2120)

延迟处理

  • getStackHook 重命名为 getErrorHook (#5201, 258ca1ec)
  • 在 jQuery.Deferred.exceptionHook 中尊重源映射 (#3179, 0b9c5037)
  • 将主分支重命名为主分支 (a32cf632)

已弃用

尺寸

  • 为不可靠的TR尺寸添加FF偏移属性回退方案 (#4529, 3bbbc111)

文档

  • 修复注释中的若干小问题 (e4d4dd81)
  • 更新 README 中的 herodevs 链接 (#5695, 093e63f9)
  • 将 CONTRIBUTING.md 与 3.x-stable 版本对齐 (d9281061)
  • 更新 CONTRIBUTING.md (4ef25b0d)
  • 在README中添加版本支持章节 (cbc2bc1f)
  • 将剩余HTTP网址更新为HTTPS (7cdd8374)
  • 修复包内 README 中的模块链接 (ace646f6)
  • 更新 CONTRIBUTING.md 中的监视任务 (77d6ad71)
  • 修复 codespell 发现的拼写错误 (620870a1)
  • 从读我文件中移除过时的Gitter徽章 (67cb1af7)
  • 从PR模板中移除“Grunt构建”部分 (988a5684)
  • 从 README 中移除过期徽章 (bcd9c2bc)
  • 更新已发布包的 README (edccabf1)
  • 从 GitHub Actions 评论中移除 git.io (016872ff)
  • 更新 README 中的 webpack 官网链接 (01819bc3)
  • 添加指向欢迎提交补丁和求助问题的链接 (924b7ce8)
  • 添加新CLA预览链接 (683ceb8f)
  • 修正错误的 trac-NUMBER 引用 (eb9ceb2f)
  • 从旧版jQuery源代码中移除过期链接 (#4997) (ed066ac7)
  • 从源代码中移除指向网络档案馆的链接 (#4981, e24f2dcf)
  • #NUMBER Trac 问题引用替换为 trac-NUMBER (5d5ea015)
  • 更新 CONTRIBUTING.md 中指向最新 jQuery 构建版本的 URL (9bdb16cd)
  • 从拉取请求模板中移除CLA复选框 (e1248931)
  • 将 IRC 服务商更新为 Libera 并修复 LAMP 死链 (175db73e)
  • 更新GitHub问题模板中的常见问题报告 (7a6fae6a)
  • 将 JS Foundation 提及项修改为 OpenJS Foundation (11611967)
  • 添加 SECURITY.md 文件,展示安全邮箱地址 (2ffe54ca)
  • 修正拼写错误 (1a7332ce)
  • 更新 jsdom 存储库链接 (a62309e0)
  • 在 README 中使用 https 协议处理超链接 (73415da2)
  • 从 README 中移除对 event/alias.js 模块的提及 (3edfa1bc)
  • 更新EdgeHTML问题链接至Web Archive访问路径 (1dad1185)
  • 引导用户通过 GitHub 文档克隆仓库 (f1c16de2)
  • 在 README 中将 OS X 改为 macOS (5a3e0664)
  • 将大部分URL更新为HTTPS (f09d9210)
  • 将Homebrew链接从HTTP转换为HTTPS (e0022f23)

效果

效果

事件

  • 在 beforeunload 中使用 .preventDefault() (7c123dec)
  • 在leverageNative中增强内部原生事件的健壮性 (#5459, 527fb3dc)
  • 避免 jQuery.event.special 与 Object.prototype 之间的冲突 (bcaeb000)
  • 简化leverageNative中保存数据的检查逻辑 (dfe212d5)
  • 使触发器(焦点/失焦/点击)与原生处理程序协同工作(#50156ad3651d)
  • 通过 focusin/focusout 在 IE 中模拟焦点/失焦效果 (#4856, #4859, #4950, ce60d318)
  • 避免在 .on(focus).off(focus) 后破坏焦点触发机制 (#4867, e539bac7)
  • 使焦点重新触发时不再将焦点返还至原始元素 (#4382, dbcffb39)
  • 元素在模糊状态下移除时不再导致崩溃 (#4417, 5c2d0870)
  • 移除事件对象的shim处理(#3235, 1a5fff4c)
  • 移除 jQuery.event.global (18db8717)
  • 仅向接受数据的对象附加事件——真正意义上的(#4397, d5c505e3)
  • 停止对焦点进入和焦点离开事件的临时处理 (#4300, 8a741376)
  • 防止leverageNative注册重复的虚拟处理程序 (eb6c0a7c)
  • 修复多个异步焦点事件的处理 (#4350, ddfa8376)

数据操作

  • 确保 jQuery.cleanData 在清理过程中不跳过元素 (#5214, 3cad5c43)
  • 将测试泛化以支持 IE (88690ebf)
  • 支持 $el.html(selfRemovingScript) (#5378) (#5377, 937923d9)
  • 将 domManip 提取到单独文件 (ee6e8740)
  • 脚本中保留HTML注释 (#4904, 2f8f39e4)
  • 在 DOM 操作中尊重脚本的 crossorigin 属性 (#4542, 15ae3614)
  • 避免在 buildFragment 中进行字符串拼接 (9c98e4e8)
  • 将jQuery.htmlPrefilter改为恒等函数 (90fed4b4)
  • 选择器:尽可能使用 nodeName 实用程序以节省大小 (4504fc3d)

偏移量

  • 查找’真实’偏移量父节点时增加搜索深度 (556eaf4a)

版本发布

  • 4.0.0 (4f2fae08)
  • 从主分支移除 dist 文件 (c838cfb5)
  • 4.0.0-rc.2 (97525193)
  • 更新 AUTHORS.txt (c128d5d8)
  • 修复4.0.0-rc.1版本发布期间发现的发布问题 (a5b0c431)
  • 从主分支移除发行版文件 (9d06c6dd)
  • 4.0.0-rc.1 (586182f3)
  • 在发布后阶段运行 npm publish (ff1f0eaa)
  • 仅在发布期间运行无浏览器测试 (fb5ab0f5)
  • 发布时暂时禁用测试运行 (3f79644b)
  • 发布时发布 tmp/release/dist 文件夹 (#5658, a865212d)
  • 修正验证中的构建日期;其他改进 (53ad94f3)
  • 从主分支移除 dist 文件 (be048a02)
  • 4.0.0-beta.2 (51fffe9f)
  • 确保构建版本正确 (3e612aee)
  • 在配置文件中设置 preReleaseBase (1fa8df5d)
  • 修复 Windows 环境下发布前后脚本的运行问题 (5518b2da)
  • 更新 AUTHORS.txt (862e7a18)
  • 将发布流程迁移至 release-it (jquery/jquery-release#114, 2646a8b0)
  • 向发布版本添加工厂文件 (#5411, 1a324b07)
  • 直接使用 buildDefaultFiles 并传递版本号 (b507c864)
  • 同时复制 dist-module 文件夹 (63767650)
  • 仅将版本化文件发布至CDN (3a0ca684)
  • 从发行版 package.json 中移除脚本和开发依赖 (7eac932d)
  • 更新 Release.generateArtifacts 中的构建命令 (3b963a21)
  • 在 Windows 中添加对 MD5 校验和的支持 (f088c366)
  • 取消全局安装 Grunt 的要求 (b2bbaa36)
  • 升级发布依赖项 (967af732)
  • 移除未使用的 chalk 依赖项 (bfb6897c)
  • 使用仓库内的 dist README 固定文件 (358b769a)
  • 更新 AUTHORS.txt (1b74660f)
  • 更新 AUTHORS.txt (cf9fe0f6)

选择器

  • 移除 :has 的临时解决方案;在 iPhone 和 iPad 上均进行测试 (65e35450)
  • 正式废弃 jQuery.expr[ “:” ]/jQuery.expr.filters (329661fd)
  • 使 selector.js 模块依赖于 attributes/attr.js (#5379, e06ff088)
  • 从多个模块中移除对 selector.js 的依赖 (e8b7db4b)
  • 重新暴露 jQuery.find.{tokenize,select,compile,setDocument} 方法 (#5259, 338de359)
  • 停止依赖 CSS.supports( “selector(…)” ) (#5194, 68aa2ef7)
  • 将 jQuery 选择器上下文逻辑回溯移植至原生选择器 (#5185, 2e644e84)
  • 使选择器列表重新支持 qSA (#5177, 09d988b7)
  • 实现可链式方法 uniqueSort (#5166, 5266f23c)
  • 重新引入 selector-native.js (4c1171f2)
  • 操作:修复模板内容内的 DOM 操作 (#5147, 3299236c)
  • 弃用旧版伪类支持,测试自定义伪类 (8c7da22c)
  • CSS.supports(selector(...)) 不兼容,则使用 jQuery :has (#5098, d153c375)
  • 移除针对 Chrome <=77 的 “a:enabled” 变通方案 (c1ee33ad)
  • 使空属性选择器在 IE 中重新生效 (#4435, 05184cc4)
  • 在uniqueSort中采用浅文档比较 (#4441, 15750b0a)
  • 添加对逗号后无效选择器的抛出测试 (6eee5f7f)
  • 使带前导组合符的选择器重新使用 qSA (ed66d5a2)
  • 使用浅文档比较避免 IE/Edge 崩溃 (#4441, aa6344ba)
  • 缩小代码体积,简化 setDocument 实现 (29a9544a)
  • 尽可能利用 :scope 伪类 (#4453, df6a7f7f)
  • 恢复 querySelectorAll 快捷用法 (cef4b731)
  • 将 Sizzle 内联到选择器模块中 (47835965)
  • 将 Sizzle 测试移植至 jQuery (79b74e04)

支持

  • 确保支持 div 的显示属性设置为 block (#4832, 09f25436)

遍历

  • 修复 IE 中带子元素的 <object> 上的 contents() 方法 (ccbd6b93)
  • 修复含子元素的 <object> 对象上的 contents() 方法 (#4384, 4d865d96)
元素周期表抱枕

本文文字及图片出自 jQuery 4.0.0,由 WebHek 分享。

共有{227}精彩评论

  1. 早年人们常使用BackboneJS[1]实现此类需求。令人惊讶的是,它至今仍在积极维护中[2]。

    若因历史遗留原因仍需使用jQuery,BackboneJS可作为迈向现代框架的理想过渡方案。该框架轻量且易于掌握

    [1]: https://backbonejs.org/

    [2]: https://github.com/jashkenas/backbone/tags

    • 这让我想起多年前jQuery的意大利面代码怪兽,其中不乏Backbone相关案例。回过头看,过度设计的React代码可能比结构合理的jQuery代码更糟糕,但某些jQuery乱码确实比任何React代码都更不堪。所以我想说的是:React确实提升了质量门槛和标准——但有时过度追求反而适得其反,审慎使用熟悉的老工具反而能高效完成任务。

      • 你让我想起2021年初某位客户要求我在React/Redux构建的文件上传器上添加功能的事。

        绝非虚言,当时存在30多个Redux动作以极其晦涩的方式串联,表单仅包含文本输入框、文件浏览器按钮和提交按钮。

        罗马尼亚团队耗费数周才搭建完成,后来该团队被调离岗位,导致无人能接手维护。

        我记得自己写了满满几页笔记才理清那些极其复杂的动作链,几小时后成功简化流程——删掉几个动作就宣告进展。太棒了。

        突然间我恍然大悟:我完全可以重写代码。

        短短数小时内,我彻底清除了那堆垃圾代码,用单一的useState替换,同时实现了新需求功能。

        客户对我的进展难以置信,更惊讶于我同时解决了他们之前遗留的诸多问题。

        随后我有了第二个顿悟:React同样毫无用处,最终弃用转而采用原生HTML表单和少量JS回调。

        • > 我没开玩笑,当时有30多个Redux动作以最令人费解的方式串联着

          我百分百相信这种描述,因为它概括了我见过的所有Redux代码库。这个库简直是间接操作的反模式典范。

          • 这听起来像是工程质量问题而非工具问题。

            结构良好的 Redux(或 MobX、Zustand)完全能实现高可维护性与高性能,反观那些遍布草率 useState 调用和深度属性钻取的代码库则不然。

            Redux Toolkit作为内置电池的解决方案,长期以来都是使用Redux的优质选择https://redux-toolkit.js.org/

            但Redux在React早期阶段的流行,导致大量Redux代码库至今仍存续,其中许多已成为遗留系统。

          • 实在无法理解有人竟为简单用例编写30个Redux动作——毕竟有人已实现完全相同的方案。不过确实,我自己也不太喜欢Redux。

            • 请对那些急需保障就业的资深专家架构师全栈开发者(刚从训练营毕业)心怀怜悯。

              • 最初的开发者并非速成班出身,而是工程专业毕业生。

                正如FAANG公司充斥着靠LeetCode黑带头衔写垃圾代码的骗子,罗马尼亚显然也是如此。

                • 这简直是对整个国家行业劳动力的极端概括。

                  若有人仅凭一则轶事(即便属实)就如此评价你的国家,你肯定会百分百赞同吧?

                  • 这与罗马尼亚或其他国家无关。

                    我想强调的是:毕业与否与处理高阶抽象概念和复杂问题的能力关联甚微——无论是编程训练营学员还是工程专业毕业生,都缺乏构建复杂系统的实战经验,更遑论在时间紧迫、技术领导和管理压力下的实战能力。

                    很可能原作者们曾遭遇这样的处境:上级要求用不熟悉的技术构建简单表单,结果写出了混乱不堪的代码。

            • 多年前我曾用Redux构建实时流数据处理层。核心需求是接收、合并并处理多条数据流,最终汇聚成单一实时数据池。此后,实时数据的消费变得轻而易举。

              即便现在,我仍不确定能否找到更适合处理实时数据和同步的工具。但对于简单的CRUD操作,Redux大多是过度设计了

      • React让复杂交互式UI的管理比jQuery轻松得多,但这也导致许多开发者仅仅因为技术可行就添加了更多复杂性。

      • 我理解你的意思是React提高了门槛,但也降低了上限。

      • >> 这让我回想起多年前jQuery的意大利面代码怪兽,有些还和Backbone有关联。

        公平地说,jQuery是针对2000年代初IE和JS版本混乱的解决方案。它让开发者无需在三种浏览器变体间反复调试。

    • 我曾采用这种方法,确实比2010年代风格的jQuery混乱代码更高效。它也很适合用户脚本场景——当你试图解决的问题范围有限时,依赖项(尤其是涉及构建步骤的依赖)会带来麻烦。请注意,除非你必须支持古老浏览器,否则完全不需要 jQuery:querySelector、addEventListener、innerHtml 这些方法作为基础构建块,早已长期可用且稳定。

      • 遗憾的是,如今编写用户脚本比以往困难得多。多数网站都采用某种响应式前端框架,因此需要大量使用mutationObservers(或jQuery中的等效实现)。

        • 我虽非前端开发者,但设计了这个方案并在用户脚本中频繁使用。虽然效率不高(完全可以重构为创建MutationObserver单例,再让每次调用挂钩到该实例),但完全满足我的需求,让我能用老派方式处理响应式网站(只要你接受异步操作):

              function awaitElement(selector) {
                  return awaitPredicate(selector, _ => true);
              }
          
              function awaitPredicate(selector, predicate) {
                  return new Promise((resolve, _reject) => {
                      for (const el of document.querySelectorAll(selector)) {
                          if (predicate(el)) {
                              resolve(el);
                              return;
                          }
                      }
          
                      // 创建MutationObserver监听变化
                      const observer = new MutationObserver((_mutations, obs) => {
                          // 可仅在_mutations内搜索而非整个DOM
                          // 效率主要取决于选择器的精确度
                          for (const el of document.querySelectorAll(selector)) {
                              if (predicate(el)) {
                                  resolve(el);
                                  obs.disconnect(); // 切记断开观察器连接!
                                  break;
                              }
                          }
                      });
          
                      // 开始观察文档
                      observer.observe(document.documentElement, {
                          childList: true,
                          subtree: true,
                          attributes: false,
                          characterData: false,
                      });
                  });
              }
          
        • 用LLM编写代码更轻松。一次性项目(我处理用户脚本的方式)正是它们大放异彩的领域。

          我用Instagram干的那些可怕事啊…

        • 我常选择setInterval而非MutationObserver,因为它能稳定运行,我不需要即时响应,也不必费心去深究原理。

        • 确实如此。这取决于你遇到问题的网站类型?我刚检查了自己的脚本,这些都是针对完全服务器渲染网站(如HN或phpBB论坛)的生活质量优化。

          • 没错,我主要用它优化工作相关的体验——比如Jira、Bitbucket、GitHub、Linear等雇主使用的工具。2010年代初这类软件大多采用全服务器渲染,如今这种情况已相当罕见。

            我懒得动手,就让大型语言模型代劳。它们偏爱用setInterval代替mutationObservers,只要能用我就忍受低效。

            • Atlassian套件的扩展性尤其糟糕——其UI调用的API接口多如牛毛,且绝大多数响应极其迟缓。

    • 我最后一个大型jQuery应用最终采用了类似的响应式模式。当时必须把自研搜索引擎前端硬塞进Joomla CMS框架,而我几乎没法修改任何代码。真是段难忘的经历!

    • 这确实是个很棒的模式。若添加信号,更新函数甚至会自动调用。这基本就是我们在[Reactive Mastro](https://mastrojs.github.io/reactive/)中所做的工作 😉

    • MobX会愉快地自动调用jQuery函数。

  2. 至今仍是全球我最钟爱的库之一。我永远热爱jQuery,它成就了我进入(真正)企业的职业生涯。

    jQuery永存!去传播吧!

    • 这些追逐新框架的小伙子们…jQuery和.NET框架一直养活着我!

    • 深有同感,我用jQuery十五年了,它就是我的首选。

    • 真该有人把虚拟DOM接入jQuery,它拥有完整的插件生态,人工智能完全可以复用。jQuery+jQuery UI+jQuery插件+人工智能,这可能是我们都忽略的超级力量。

      • 初读时还以为你在开玩笑说虚拟 DOM 集成。但现在看来是认真的。

        既然已有真实的 DOM,何必再搞虚拟的?除非你指的是 AI 目前还无法可靠地与真实浏览器交互。

  3. 每当这里提到HTMX,我总忍不住想:“这不就是用几行语法怪异的代码替换原生jQuery的三行命令式代码吗?”

    反正jQuery一直能解决问题,只要它管用就永远用下去吧。

    • 没错:HTMX源于intercooler.js,后者基于jQuery并受jQuery.load()方法启发:

      https://api.jquery.com/load/

      我是在某次性能优化工作中发现它的。intercooler.js最初是个自定义函数,通过自定义属性钩子拦截.load()事件(这个技巧源自Angular 1.x)

      我对jQuery怀有深深的敬意与热爱

    • jQuery的问题在于其命令式特性——当需要处理多项任务时,必须命令式地覆盖所有情况,导致代码迅速变得复杂。

      • 没错,这正是另一个HN禅宗公案:“如果你…可能就不需要React”。但若你用jQuery/原生JavaScript把状态硬塞进HTML,那你确实需要React这类框架。

      • 我部分认同这种观点,2015年的我曾是SPA的狂热信徒,但如今当我看到采用PHP和jQuery美学标记的网站(而非Facebook Marketplace那种架构)时,总会松一口气。并非说我愿意用它们编程,但欣赏它们运行(或崩溃)的可预测性,通常不会让浏览器卡死。或许是因为那些使用jQuery却幸存下来的网站,正是因为它们的复杂度始终未超过某个极低的临界点。

        • 讽刺的是,Facebook其实是用PHP开发的。

          • 曾经确实如此,所以他们才转向HHVM进行解释执行,但如今已被名为Hacklang的PHP衍生语言几乎完全取代。

          • 我认为到2026年Facebook将演变为多元技术集合体…绝对不再仅依赖PHP。

    • 如今我已转向原生JS,但$()选择器接口相较document.getElement[s]by[attribute)]确实优雅简洁得多。

      虽然原生选择器可能比jQuery慢,但或许能通过预计算优化。

      • 若你尚未了解:请关注querySelector和querySelectorAll。它们更接近jQuery选择器系统的运作方式,我认为正是受其启发而生。

        若嫌冗长,可定义简短名称的实用函数(尽管我个人不热衷此类做法)。

        https://developer.mozilla.org/docs/Web/API/Document/querySel

        https://developer.mozilla.org/docs/Web/API/Document/querySel

        https://developer.mozilla.org/docs/Web/API/Element/querySele

        https://developer.mozilla.org/docs/Web/API/Element/querySele

        • body.qsa(‘.class’).forEach(e=>): 是的,在Node原型中添加qs()和Array.from(qsa())别名,并在window中添加.body属性,就能省下成千上万次键盘敲击。之后若想发挥创意,可以尝试使用Proxy,但我从未觉得有必要。

          • 不过请别乱改原生原型。

            • 如果你是库开发者,请同意此观点。若你是应用或网站开发者,那这是你的项目。其他人应避免向原生原型添加内容,以确保最终用户体验的纯净性。

              • 作为应用或网站开发者,至少你不会破坏他人的系统。

                但你仍可能破坏自身项目。试想:当你为原生原型扩展方法后,原生原型后续却新增了同名方法。

                新版本库开始采用这个标准方法。

                当你升级网站依赖的库或新增依赖时,新代码恰好依赖该原生原型。而你早已用自定义方法覆盖了它——且该方法行为必然存在差异。你破坏了新代码的运行,而修复过程可能相当棘手——因为自定义方法的调用遍布代码各处。

                这种做法仅适用于零依赖项目,或依赖项永不更新的场景。

                或者你可以规避风险,直接定义一个带参数的节点方法。

                这同样关乎良好习惯的养成:你当前在项目中可能习惯性地扩展原型,但当你编写库时,能否记得避免这种做法?

                顺便问一句,你怎么能确定不会将应用程序中的某些代码移入库?毕竟你喜欢这些实用函数,并希望在其他项目中复用它们。何不将共享代码开源,直接通过NPM安装?咔嚓一声,这东西就成了库。

                • > 当你升级网站依赖的库或新增依赖时,新代码恰好依赖了原生原型。而你用自定义方法替换了原型,且该方法行为可能不完全一致。你破坏了新代码的兼容性,修复过程可能相当棘手——因为自定义方法的调用遍布整个代码库。

                  他建议的是添加原型方法而非替换。除非所用库本身也在添加原型,否则我看不出问题所在。当然,若未来JS版本占用这些命名可能会导致故障,但我敢打赌实际应用中这根本不成问题。

                • 规则衍生规则。

      • $(和$$)选择器函数在Chrome/Chromium开发工具中依然可用!

      • const $ = document.querySelector.bind(document);
        const $$ = document.querySelectorAll.bind(document);

      • jQuery却像Svelte那样被编译掉…这主意倒不赖。

        • 我不想显得像个刻板印象中的web开发者,但querySelector的解析步骤明明被缓存了,速度不至于慢到需要保留这种构建步骤。

          • 有些东西你构建它,并非因为必要,而是因为你有能力做到。

      • 实现非常简洁的jQuery,包含所有常用API:

          (function (global) {
            function $(selector, context = document) {
              let elements = [];
        
              if (typeof selector === "string") {
                elements = Array.from(context.querySelectorAll(selector));
              } else if (selector instanceof Element || selector === window || selector === document) {
                elements = [selector];
              } else if (selector instanceof NodeList || Array.isArray(selector)) {
                elements = Array.from(selector);
              } else if (typeof selector === "function") {
                // DOM ready
                if (document.readyState !== "loading") {
                  selector();
                } else {
                  document.addEventListener("DOMContentLoaded", selector);
                }
                return;
              }
        
              return new Dollar(elements);
            }
        
            class Dollar {
              constructor(elements) {
                this.elements = elements;
              }
        
              // Iterate
              each(callback) {
                this.elements.forEach((el, i) => callback.call(el, el, i));
                return this;
              }
        
              // Events
              on(event, handler, options) {
                return this.each(el => el.addEventListener(event, handler, options));
              }
        
              off(event, handler, options) {
                return this.each(el => el.removeEventListener(event, handler, options));
              }
        
              // Classes
              addClass(className) {
                return this.each(el => el.classList.add(...className.split(" ")));
              }
        
              removeClass(className) {
                return this.each(el => el.classList.remove(...className.split(" ")));
              }
        
              toggleClass(className) {
                return this.each(el => el.classList.toggle(className));
              }
        
              hasClass(className) {
                return this.elements[0]?.classList.contains(className) ?? false;
              }
        
              // Attributes
              attr(name, value) {
                if (value === undefined) {
                  return this.elements[0]?.getAttribute(name);
                }
                return this.each(el => el.setAttribute(name, value));
              }
        
              removeAttr(name) {
                return this.each(el => el.removeAttribute(name));
              }
        
              // Content
              html(value) {
                if (value === undefined) {
                  return this.elements[0]?.innerHTML;
                }
                return this.each(el => (el.innerHTML = value));
              }
        
              text(value) {
                if (value === undefined) {
                  return this.elements[0]?.textContent;
                }
                return this.each(el => (el.textContent = value));
              }
        
              // DOM manipulation
              append(content) {
                return this.each(el => {
                  if (content instanceof Element) {
                    el.appendChild(content.cloneNode(true));
                  } else {
                    el.insertAdjacentHTML("beforeend", content);
                  }
                });
              }
        
              remove() {
                return this.each(el => el.remove());
              }
        
              // Utilities
              get(index = 0) {
                return this.elements[index];
              }
        
              first() {
                return new Dollar(this.elements.slice(0, 1));
              }
        
              last() {
                return new Dollar(this.elements.slice(-1));
              }
            }
        
            global.$ = $;
          })(window);
        
      • const $ = document.querySelectorAll

    • 至少在用Django时,我基本靠HTMX和原生JS解决大部分问题。这样既保持简洁,又能让应用具备SPA体验。

  4. 经典的jQuery啊。

    感谢你为我们所做的一切。

  5. 欣慰它仍在更新。可悲的是,这大概意味着React会存在到2060年。

    • React有什么问题?

      比起意面般的jQuery,它让应用开发高效太多。

      至今想起追踪jQuery回调的噩梦就浑身发抖

      • 复杂的API需要深入理解内部机制及其陷阱。

        摒弃类组件后,其渲染模型复杂且生命周期难以掌控。要实现高性能网站极其困难(但欢迎用React作品链接反驳我)。

        更严重的问题在于:大量网站滥用React构建静态内容,这些项目根本不具备“应用程序特性”,也无需实时响应能力。超过95%的React“应用”本该采用模板语言开发。

        例如Github早期使用Ruby时各方面表现都优越得多,可惜有人必须向高层推销晋升方案。

      • 它过于冗长且反直觉,而在2025年,虚拟DOM已非编写交互式网页应用的必要条件。若想开发现代网页应用,可选用Svelte;若追求真正的函数式编程,Elm才是理想选择。React不过是当代的jQuery——它在Angular时代确实功不可没,但我们正站在新时代的黎明。

        • 在2025年推荐Elm纯属无稽之谈——这话出自Elm爱好者之口。

          • 作为非Elm爱好者,为何如此断言?我认为当下任何JS前端框架都能冻结版本沿用十年。JS具有极强的向后兼容性。

            真正引入漏洞并需要持续维护的,是那些涉及服务器连接的框架。

        • 为何说它过于冗长?

          我认为它非常直观,除了useEffect这个特性。

        • Svelte初看不错,直到你发现要获得最佳支持和功能,基本必须使用糟糕的元框架SvelteKit。

      • React的问题在于它解决了前端开发。

        因此选择只有两种:1. 整天写React代码并乐在其中;2. 编造理由指责它不好。

        这个领域里许多才华横溢且求知若渴的人都倾向于选择2。

        • 我认为React的问题在于它过于霸道,且为解决许多问题而过度设计得令人恼火。我使用Mithril,发现它简单得多。

          • 当他们开始添加新钩子来绕过自身残缺的组件/渲染生命周期时,我就知道React注定会变成臃肿的烂摊子。

            正常人根本不会记得为那些冷门场景使用useDeferredValueuseEffectEvent

            这些都是React糟糕组件生命周期设计的直接后果。反观Vue的精细化生命周期钩子,既能提供所需控制权又无需绕弯子,命名也合乎逻辑[1]

            更别提React那套用Context实现的全局状态管理的拙劣方案了。每次状态变更都会触发整棵树的重渲染,即便组件并未订阅该状态——简直是性能噩梦。想要订阅功能?要么手动实现,要么使用第三方状态库。但若全局状态依赖于React生态中的其他状态/数据,这些库又无法在组件渲染前完成初始化。

            1. https://vuejs.org/api/composition-api-lifecycle.html

            • 我完全尊重人们选择避开React的决定,但作为开发过多个React应用的从业者,我仍想回应部分观点。

              > 当他们开始添加新钩子来规避自身缺陷的组件/渲染生命周期时,我就知道React注定会沦为臃肿的烂摊子。

              钩子并未带来根本性变革。它们只是逃离渲染循环的手段——而类组件早已具备这种能力。

              > 正常人根本不会记得在极其小众的场景里使用useDeferredValueuseEffectEvent

              或许因为你未必需要它们。不过说句公道话,我在旧版React时代就用过这些功能,甚至在工作中用它们搭建过完整的单页应用。但读了文档后,感觉它们挺靠谱?

              > 更别提React用Contexts管理全局状态的拙劣方案了——每次状态变更都触发整棵树的重渲染,简直是性能噩梦

              我觉得有必要说明重渲染的本质:它不同于DOM重绘,甚至消耗的CPU周期数量级都不同。整个网站可能因文本输入而重渲染,但即使开发工具显示CPU降速10倍,你也很难察觉——除非你无端在渲染周期中添加耗时操作。确实有人在文本输入每次变更时执行fetch请求。而同样的降速操作在基于Svelte的Apple Music上几乎会导致崩溃。

              但几乎所有其他状态管理库都能实现你描述的理想效果。

            • Vue究竟优越在哪里?在我看来它只是引入了更多人为状态。

              我对React的主要质疑在于其异步处理机制,但这源于异步流程本身难以建模的特性。Suspense虽有所改善,但我并不喜欢它。我强烈认为中间状态应当显式呈现。

            • 我采用老派方式使用Preact,完全不依赖React引入的“use-whatever”机制。这种简洁易用的特性让我能快速完成开发,无需过度思考。

    • 到2060年 React Native 应该会升级到v0.93

    • 事实上已经存在两个 React 版本。到2060年,将会有五个版本。

  6. 就像我确信其他在2000年代和2010年代初接触Web开发的人一样,在SPA框架尚未普及的年代,我正是通过jQuery学习Web开发脚本编写。很高兴看到它依然活跃。早期基于jQuery构建的众多项目至今仍能正常运行。向开发团队致敬。

  7. 祝贺所有参与jQuery 4.0发布的同仁。

    值得一提的是,若您寻求基于jQuery的更结构化方案,JsViews(https://jsviews.com)提供成熟稳定多年的响应式模板与数据绑定系统。

    虽然它不像新框架那样广受欢迎,但对偏爱jQuery生态系统的开发者仍具吸引力。

    • 从未听说过JsViews,但看起来很有意思。至于其他“现代”的jQuery方案,我个人更倾向于cheerio和alpine:

      https://cheerio.js.org/

      https://alpinejs.dev/

    • 看起来很有意思。虽然近期不太可能编写 jQuery 代码,但我会研究其源代码看看能否从中汲取经验。

      关于采用率问题,JsViews 官网的页面设计让我误以为自己不小心在 Iceweasel 浏览器里切换了“桌面版网站”选项,不知是否因此吓退了用户。或者正如其他人提到的,如今大多数jQuery开发都存在于遗留代码库中,开发者被禁止添加任何新库,这使得新jQuery库的采用率远低于基于jQuery用户基数预期的水平。

      (不过网站确实能正常运行,加载速度也快。这正是我始终欣赏当今存活的jQuery网站之处。唯一缺失的是压缩+gzip后的文件大小提示。编辑:jsrender.js为33.74 kB,jsrender.min.js仅12.82 kB)

      • 我一直在与JsViews的作者鲍里斯合作,我们确实计划对网站进行现代化改造——这正应了你关于第一印象和用户接受度的观点。你完全正确,呈现效果至关重要;如果界面显得过时,人们可能在深入了解前就失去兴趣。

        我也向鲍里斯提出了jQuery依赖性问题,原因正是你所指出的:许多团队会自动排除任何需要jQuery的方案,尤其在非遗留代码库中。这确实是当前的实际障碍。

        值得一提的是,无jQuery版本或许会实现。鲍里斯正在积极探索,但无法保证——这并非简单重构,而是需要彻底重写的重大工程。

  8. 难以置信的是,它居然仍支持IE 11——该浏览器在jQuery 5.0中已被计划弃用

    • 这似乎是为了不延误4.0版本的发布。由于他们遵循半版本号规则,意味着IE 11要到5.0才被砍掉[1]。考虑到3.0版本发布已逾十年,这实在令人咋舌。

      不过这次或许规划得更周全:早在2019年他们就宣称要在2020年发布4.0版本!

      [1]: https://github.com/jquery/jquery/pull/5077[2]: https://github.com/jquery/jquery/issues/4299

    • 向后兼容性。显然仍有部分用户停留在IE11环境。jQuery持续支持这些用户及其仍在运行的产品,这点值得称道。

      • 最让我费解的是这部分:

        > 我们同时终止了对其他老旧浏览器的支持,包括旧版Edge、iOS近三代之前的版本、Firefox近两代之前的版本(Firefox ESR除外)以及Android浏览器。

        2022年发布的iOS 16版Safari在所有可想象的方面都比MSIE 11更现代化。我敢打赌,受困于iOS 16的用户数量远超仅能使用IE 11的群体——除非是在那些IT部门极其糟糕的企业中,这种情况反而可能助长他们的低效。

        我主张果断撕掉这块创可贴。MSIE已是死技术,比微软其他淘汰的浏览器更过时。让它尽快在耻辱中消亡吧。

        • 这里的“支持”大概指“我们在这些浏览器上测试jQuery兼容性”——iOS 16的Safari很可能仍能流畅运行该版本jQuery。但为这些客户端运行自动化测试套件或支持漏洞修复,远比启动微软提供的IE11虚拟机困难得多。

        • 许多企业内网应用仍依赖IE,而微软至今仍在维护IE。即便在Windows 11系统中,Edge浏览器也为此保留了IE模式。反观iPhone用户若固守旧版iOS系统,则意味着已不再获得苹果官方支持。

          • 内部僵尸应用用旧浏览器,上网就用现代浏览器。

            这些手机仍在支持范围内。最新iOS 16更新发布于2025年9月。

        • 问题很少出在糟糕的IT部门,而是某些特殊或遗留软件缺乏现代替代方案

        • > 2022年发布的iOS 16版Safari,在所有可想象的方面都比MSIE 11更现代化。

          仍在运行MSIE11的计算机可能数以百万计,甚至上千万。而运行iOS 16的设备可能已不复存在

          • > 运行iOS 16的设备几乎不存在

            我的iPhone X卡在iOS 16系统无法升级。

            但手机仍运行良好。尽管每日使用长达8年,电池容量仍达81%,从未摔落过,OLED屏幕效果出色,可录制4K@60帧视频。其响应速度远超2025年售价200美元的全新安卓手机(如小米)。苹果仍持续提供安全补丁。相较现代iPhone的唯一短板是弱光拍摄表现。此外部分应用开发者已停止支持iOS 16,例如我无法使用ChatGPT应用,只能通过浏览器访问,但Gemini应用运行正常。

          • 据Cloudflare数据显示,当前几乎没有任何版本的MSIE用户存续。[0]

            Statcounter统计显示约4.6%的iOS用户仍在使用iOS 16系统。[1]

            我直觉认为,当前使用iOS 16的人数是任何版本MSIE用户的数倍之多。

            [0] https://radar.cloudflare.com/reports/browser-market-share-20

            [1] https://gs.statcounter.com/os-version-market-share/ios/mobil

            • 2020年我参观过一家酿酒厂。他们的机器由运行Windows XP的惠普笔记本电脑管理。那些机器、那些笔记本电脑以及那个Windows XP系统,很可能至今仍在使用,连同它们陈旧的IE浏览器。

              • 这些设备大概会一直用到电容报废为止,但关键在于它们几乎肯定运行着某些Win32工业流程软件——这类软件根本不需要网页浏览器,甚至无需联网。考虑到老旧WinXP系统的安全漏洞,我倒希望它们没接入无线网络!

              • 这些机器很可能根本没联网。

              • XP最多只支持IE8

            • 据我所知,公共统计数据往往忽略企业网络。

      • 但那些用户/产品会升级jQuery吗?

      • 谁还在用IE11——为什么?

        • 我过去有个客户,截至2020年仍有显著比例的IE8/9/11流量。所谓显著是指百万用户中占比超10%。

          这些流量遵循周一至周五8-17点的规律。

          本质上是用户在工作设备(邮局、银行等)上使用企业电脑,而这些设备未安装现代浏览器。

          我们专门配置了测试机,用于在IE8和IE9环境下手动验证每次版本发布。

          只要用户通过这些设备访问我们的产品,我们绝不会放弃支持。

          但据我所知,该客户已于2024年终止对IE8和IE9的支持,IE11也计划在今年停止维护。

        • 确实存在一些极其保守的政府机构和大型企业,仍在运行十年老旧的基础设施。若这构成你的客户群体?那就得配合。此外我曾参与某消费类产品发布网站开发——你或许记得那项目——后来突然要求支持IE7,只因日本高管们用的就是这个版本。客户根本不在乎,但我们确实实现了IE7兼容。

        • 部分企业设备仍在运行XP。既然能用,何必升级?

        • 我认为任何仍在使用ActiveX之类组件或“原生”功能的东西。当然,这些都该被淘汰了,但有些可能尚未被淘汰,据我所知,这些组件根本没有前途。

        • 现在肯定有人写了MSIE 11的0day漏洞,能获取系统权限并悄无声息地安装一个IE皮肤的Chromium浏览器。如果还没人做,该有人着手了。——全体用户敬上

          • XP上最后可用的Chromium版本也有0day漏洞,所以没什么大不了的。

    • 微软承诺在Windows 10 LTSC及Windows 11的Edge浏览器IE模式中支持IE 11至2032年。

    • 并非全球用户都能使用现代软硬件。大量学校机房仍在运行旧版软件。

      • 没错,那就用jQuery 3吧。

        真疯狂——居然要求IE11内的软件使用最新版本的库。

  9. jQuery为升级工具投入的精力令人钦佩,这种敬佩难以言表。

  10. 我钟爱jQuery,尤其欣赏它优雅的方法链机制——能在DOM元素对象/数组链中持续操作。

    十五年前我曾为法国用户撰写jQuery教程,浏览量颇高。但愿它助力了jQuery的普及。

  11. 令人惊叹的是jQuery至今仍被广泛使用。即便在现代网站中也常能发现它的踪迹(浏览器开发工具→控制台中的jQuery输出即可验证)。不仅业余网站如此,专业企业官网及其开发工具同样如此。

    • 好奇:

      如今取代JQ的巨无霸是什么?

      我感觉它仍是事实上的行业标准?

      • JQ引入的许多特性如今已成为浏览器原生功能。

        • 或者可以用其他技术替代,比如CSS动画,只需用addClass/removeClass就能取代jQuery的动画代码。

  12. 整整20年!记得jQuery刚发布时,我还以为5到10年内它就会被淘汰——因为所有jQuery的功能都会内置到浏览器或成为HTML规范的一部分。

    但随后谷歌、Chrome、iPhone、PWA以及无处不在的JS技术席卷而来,将网页发展引向了我当初完全无法想象的轨迹。

  13. 十五年前我用jQuery实现的所有功能,其实十年前就能通过CSS和标准JS库完成。如今看到jQuery仍在被使用,我真心感到困惑。

    如今还有什么功能是jQuery能实现而标准库几行代码做不到的吗?

    • jQuery简洁的链式语法更易读、更易记,维护起来也更愉快。重写为标准库虽易,却会因每行代码都需添加冗余模板而导致代码臃肿。

    • jQuery能用一行代码完成标准库需要数行的操作。减少代码量正是库存在的意义。

      • 直到你需要升级时才发现它会反噬你

        • jQuery的核心价值在于为不一致的浏览器实现提供统一API,因此它通常能避免反噬,而非反噬你。

  14. 真正享受网页开发乐趣始于掌握jQuery之时。它让一切变得如此简单易用!

    • jQuery让混乱的生态系统稍显统一。配合CKEditor,它曾有效驯服大量网页开发者的混乱局面——直到Node.js横空出世。=3

  15. 好,轮到你们了,script.aculo.us 和 Mootools。

  16. 如果采用服务器端渲染,仅用 jQuery 或原生 JS 就够了吗?还是值得研究更复杂的 JS 前端方案?

    • htmlx[1]是当下SSR的首选库,坦白说相当不错。它能让JS在多数场景下彻底退出舞台

      [1]: https://htmx.org/

      • 没错,我现在的默认方案是 Django + Tailwind,配合 HTMX 和 Celery 处理后台任务。仅此组合就让我事半功倍。

  17. jQuery 曾是 JavaScript 的巅峰。

    美好时光啊,很高兴它依然活跃。

    • 至今仍有大量网站在使用它。

      • 确实如此。尽管许多功能已被原生JavaScript和浏览器吸收,但jQuery的语法依然便捷得多。

  18. 感觉自己老了。职业生涯初期对jQuery又爱又恨——那时我刚接触HTML5尾声,又赶上ES6新特性横行的时代。

  19. 当浏览器间功能缺失或标准不统一时,jQuery曾极具价值。如今JS/DOM API已高度丰富、成熟且标准化,jQuery的重要性已大不如前。

    https://youmightnotneedjquery.com/

    诚然,原生JS的实现有时不够优雅,但绝大多数情况并不复杂。

    个人认为原生JS的另一优势(除节省约30KB体积外)在于调试更便捷。例如通过开发工具定位/调试事件监听器时,原生实现更直观——复杂的jQuery事件监听器往往需要反复调试大量代码。

  20. 记得当初既害怕jQuery又害怕原生JS。哎,时光飞逝啊。

    难以置信它至今仍在维护。

  21. > 包含部分破坏性变更

    多数变更完全合理——很多是内部清理(用户端无需修改代码)、弃用旧版浏览器等操作。

    但最让我意外的是存在破坏性API变更。仍在使用jQuery的项目多为遗留项目(我自己就有几个闲置项目)。破坏性变更意味着升级过程更麻烦,而这些项目本就不值得费力升级。移除jQuery.isArray这类方法只会增加升级难度——内部实现完全可以直接调用Array.isArray,至少这样不会破坏现有代码。

    这类项目在生命周期某个节点,就该接受历史定位,停止破坏与成千上万(甚至数百万!)用户项目的兼容性。只需成为一个干净利落的库,让人们能永远无需顾虑地持续使用。

    • 我不明白你的用例。既然有不想动的老项目,为什么要把依赖升级到新的大版本?你可以继续用jQuery而不必考虑升级问题。直接用3.7版,别管4版的事。

      • 修复漏洞?

        最近因客户要求(安全问题)不得不将jQuery从2版升级到最新版,结果就遇到了第三方库/插件的兼容性问题。

  22. jQuery是我最后一次感受到库能施展魔法!此后再无任何库能带来这种体验。

    • 连现代原生JavaScript都不如?

      • 现在虽然接近了,但语法冗余得多:比如document.getElementById(‘theID’) vs $(‘#theID’)

        • 几乎每次编写JavaScript时,第一行都是const $ = (selector) => document.querySelector(selector)。我虽不像这里许多人那样怀念jQuery,但这个特定的简写确实非常实用。

          为了增添趣味,还可以在顶部定义const $$ = (selector) => document.querySelectorAll(selector)。

          •     const $$ = (selector) => Array.from(document.querySelectorAll(selector))
            

            这样写更妙,因为之后就能实现:

                $$(‘.myclass’).map(e => stuff)
            
  23. 我至今仍在使用jQuery。

  24. 这个变更日志太疯狂了;它关闭了数十个在Github上开放了5年以上的issue。我猜这和这是数年来的首个新主要版本有关。

    有人做过基准测试吗?看看jQuery 4和jQuery 3.7的对比?

  25. 我依然钟爱jQuery中ajax调用的简洁性

    • jQuery提供了Fetch API不具备的功能?

      • 文件上传进度。Fetch API无法在文件上传(或处理大型请求)时观察并显示进度,而jQuery通过xhr回调实现了这一功能。

  26. 我虽非前端开发者,但曾是jQuery的重度用户。可我终究难舍初心……原型框架万岁!

  27. 对我们这些网络诞生初期就投身Web应用开发的人而言,JQ堪称奇迹。

    感谢各位!

  28. 他们支持ES6模块、可信类型和CSP太棒了!清理那些已被平台替代的旧API也令人欣慰!

  29. 关于焦点事件顺序的描述让我心跳加速,仿佛回到了十五年前的噩梦!

  30. 在React、NextJS盛行的时代,这有什么用武之地?静态网站已有Astro等工具。即便需要简单方案,为何还要用jQuery?原生JS如今API更完善了。我是不是漏了什么?

    • 我常开发定制化JS组件、游戏及工具,这些项目并不依赖React这类庞大框架。并非所有应用都是全页面SPA。原生JS确实进步了,但我发现自己总在编写类似JQ的小型库和工具来处理繁琐甚至基础的DOM操作,因此转回JQ后省了不少时间和麻烦。压缩后的JQ体积相当小巧,几乎不占空间。

      Bootstrap等框架也使用JQ(尽管我认为他们正试图剔除此类第三方依赖,因其易引发冲突)。

      我在Angular应用中也用过JQ——当标准工具无法实现复杂的即时DOM操作时,它就显得不可或缺。

    • 业余爱好者不愿学习每个新框架。有人经营小型商业网站,自2010年起就对jQuery很满意。

  31. 恭喜发布新版本!虽然很久没写jQuery了,但记得在浏览器兼容性糟糕的年代它有多好用。感谢EJohn和团队持续维护这个项目。

  32. 超棒的开源库,很高兴它仍在维护!

    • 无感。当年我是Mootools的忠实拥趸,目睹jQuery崛起时颇感遗憾。

      如今JavaScript已进化到无需依赖jQuery的阶段,这让我欣慰。

  33. 本以为会迎来更彻底的变革,结果更像是内部清理工作,比如“2026年真该没人用这个了”。他们提供这个库,是为那些真心钟爱jQuery、宁愿用它也不愿用React的人准备的(这完全合理且情有可原)。

    核心行为似乎没有改变,这正是人们抱怨的地方,例如https://github.blog/engineering/engineering-principles/remov

    这种语法虽然简单易写,但按我们的标准来看,并不能很好地传达意图。作者是否期望该页面存在一个或多个js-widget元素?此外,如果我们在更新页面标记时意外遗漏了js-widget类名,浏览器是否会通过异常提示告知我们出了问题?默认情况下,当初始选择器未匹配任何元素时,jQuery会静默跳过整个表达式;但对我们而言,这种行为是缺陷而非特性。

    我完全赞同此观点,因为此类隐蔽缺陷曾多次让我吃尽苦头。不过我理解有些人对此并不在意。

    我已确定绝不会在个人项目中使用jQuery,工作环境也绝无可能采用它(我更倾向于让框架基于数据绑定自动处理渲染)。因此这些争议与我无关。但祝jQuery及其拥趸好运。

  34. 我是长期用户。它多年来一直服务得很好,尽管自从3.0版本后我就没怎么碰过它。很高兴看到它仍在维护中。

  35. 这太重要了。对于任何需要原生JavaScript无法实现的自定义交互的网站,jQuery依然是我的首选方案。

  36. 嗯,或许我终于能告别2.x版本了

  37. 真希望它能支持XPath查询。

  38. 从未用过jQuery的人还有必要尝试吗?

    • 别被这帖里那些对jQuery念念不忘的老家伙们糊弄了——他们只是怀旧罢了。2026年根本没必要考虑用jQuery

    • 总体来说不需要,因为它实现的大部分功能现在都原生支持了,需要用到它的概率已经很低。

      若未使用框架,它或许还能简化某些特殊场景的操作,但没必要特意为用而用。

  39. 哇,干得漂亮。

  40. 哇,这很有意思。我还以为jQuery已经过时了。

    我的下一个问题是:OpenAI和Anthropic会用这个来训练数据吗?如果我让Claude Code编写应用并使用jQuery,它会调用旧版本吗?除非模型重新训练到新版本?

  41. 看到jQuery 4真是令人耳目一新

  42. jQuery是什么?我做DHTML只用Dynamic Drive

  43. 意外发现Zepto.js能完美替代我多数小型场景的原有方案。确实该试试jQuery精简版,之前从未研究过。

    • Zepto!这个名字多年未闻。记不清具体经过,但我至今仍是Github上ZeptoJS组织的成员。

      • 我真的很喜欢这个项目!为什么不交给愿意维护的人,或者至少存档保存呢?

  44. 向王者致敬

  45. 现在我们需要的是从js到llm的实时日志转发。

  46. 过去十年我在小型应用中使用jQuery从未出过问题。后来我尽可能用现代JS逐步替换它,如今发现自己仅因Datatables.js依赖它才继续使用。

    这段旅程很美好,衷心感谢所有参与开发的人。虽然可能看不到jQuery 5了,但这就是人生。

  47. 这名字真是久违了…

  48. 还得加点 jQuery 才够味

  49. [已删除]

  50. 没人喜欢$…吗?

  51. jQuery虽已升级至v4,但许多网站(尤其是WordPress)仍在使用1.11或1.12版本,仅用于实现模态框(popover)、显示/隐藏(display)或Ajax(fetch)功能。

  52. 即便迁移至ES模块后,jQuery仍显臃肿。压缩后仅4.7KB的Preact相比之下轻量得多[1]。

    [0]: https://bundlephobia.com/package/jquery@4.0.0

    [1]: https://bundlephobia.com/package/preact@10.28.2

    • > Preact仅需4.7KB

      是否存在特殊情况,即使用虚拟 DOM 框架的开发者不会额外引入 100-200KB 的“生态系统”依赖?

      虽然理论上可能存在,但我从未实际见过。倒是见过仅用 jQuery 的网站——仅需 ~27KB 就能实现丰富功能。

      • 我采用Preact构建极简前端,以适配小型嵌入式MCU的闪存ROM。整个前端经gzip压缩后约25KB,包含直接嵌入Preact压缩文件的SVG图像。我对引入的库及其对整体负载大小的影响极为谨慎。

        最初采用jQuery构建简易前端原型,但很快突破了“压缩后总大小不超过40KB”的目标。问题在于仅靠jQuery无法满足需求——我们需要jQueryUI来辅助前端开发,否则就得自行构建类似的复杂组件。当jQuery代码量变得庞大时,Preact的优势便显而易见。当前的有效负载比jQuery原型小得多。

      • 看看基于Preact的Deno + Fresh。仅用Preact就能实现很多功能

      • 我做简单SPA时就这么干。纯Vue加几个自制的微型插件。

    • 但jQuery功能更全面,还支持老旧浏览器。

      • 官方声明仅支持Chrome最新两个版本。但考虑到他们支持IE11,这其实已经相当广泛了。

      • > 包含对旧版浏览器的支持

        这正是症结所在。为了2025年可能更新jQuery的10个用户支持某个浏览器,简直是疯了。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号