多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

jquery.min.js 工作原理与高频业务场景全解析

jquery.min.js 工作原理与高频业务场景全解析 简介jQuery.min.js 是一份被广泛使用的 JavaScript 库压缩文件面向网页开发者用于简化跨浏览器的文档对象模型操作、事件绑定、动画效果和异步请求它通过移除空格、注释和换行来缩小体积压缩包共包含一个脚本文件整体大小仅有 32 千字节非常轻量可直接放入站点目录或作为离线开发备查。目前已有 183 人学习下载适合正在学习 jQuery 基础语法、选择器用法、事件处理以及 Ajax 交互的初学者也适合在旧项目中需要快速引用核心库的开发者。文件中提供了完整的核心接口覆盖选择器、属性操作、样式修改、事件委派、动画效果和异步请求等常见功能开发时可用未压缩版本调试部署时切换到该压缩版以优化性能。虽然现代前端框架众多jQuery 在大量遗留系统和企业级项目中仍有重要地位获取此资源即可获得稳定版本的 jQuery 核心库减少重复代码编写提高工作效率。 在 static/js 目录里躺着一个没有任何换行的老文件文件名叫 jquery.min.js。很多人对它是又爱又怕爱的是只要引入它操作 DOM、发 Ajax、做动画都顺手得像开了外挂怕的是打开文件想看看源码瞬间被一行几万字符的代码劝退。这篇文章我不打算把压缩源码展开给你看而是从 jquery.min.js 这个文件本身出发把它的工作原理、引入方式、高频业务姿势、经典翻车场景以及它跟现代前端的关系一次聊透。无论你是刚入门前端、还在用 HTML、CSS、JS 基础语法写页面还是已经在 Vue、React 里摸爬滚打、偶尔被老项目拉回来调 jQuery这篇都能对得上你的胃口。1. jquery.min.js 这个压缩文件凭什么活到今天1.1 名字拆开看jQuery、min、js 分别代表什么先说最直观的问题。jquery.min.js 这个名字jquery 是项目名js 是文件类型中间的 min 是 minified 的缩写意思是压缩后的版本。压缩前那个文件叫 jquery.js里面有完整的缩进、注释、变量名方便读源码压缩后所有换行和空格都被去掉变量名也尽可能缩短。我见过很多新人在这上面犯迷糊以为 min 表示最小或者mini 版实际上它只是构建产物的一种形式功能上没有任何裁剪。jQuery 本身是一个 JavaScript 库核心卖点一句话就能说清用更少的代码完成更多的 DOM 操作。在它出现的那个年代原生 JavaScript 写跨浏览器代码是非常痛苦的IE 和 Netscape 对事件绑定、DOM 查找的实现都不一样同一套代码在 IE6 里跑通了换到 Firefox 又报错。jQuery 做的事就是把这层差异抹平让你写出$(#id).click(function(){...})这样一行代码底层自动处理浏览器兼容。这也是为什么 jquery.min.js 能在十几年后依然出现在无数老项目的依赖列表里。1.2 选择器、DOM 操作、Ajax三大核心能力拆解jQuery 的设计思路可以理解成给浏览器发了一套统一遥控器。原生 JS 要找一个元素你得写document.getElementById、document.getElementsByClassName经常一长串jQuery 直接用 CSS 选择器语法$(#id)、$(.class)、$(input[namexxx])一套思路走天下。这个设计放到今天看非常自然因为后来document.querySelectorAll也是这么做的但在当年这就是降维打击。DOM 操作是另一个重头戏。$(div).css(color, red)是设置样式$(div).css(color)是读取样式同一个方法名传参不同含义就不同。还有.attr()、.html()、.text()、.append()这些都是高频工具。配合链式调用你可以$(#box).css(top, 0).fadeIn().find(span).text(done)一口气完成多步操作每一句返回的还是同一个 jQuery 对象所以能继续往下点。这种设计后来被很多库借鉴但 jQuery 是把链式调用普及到大众心里的那一款。Ajax 方面jQuery 封装的$.ajax、$.get、$.post极大地降低了异步请求的入门门槛。老项目里最常见的写法是$.ajax({ url: /api/user, type: POST, data: { name: 张三 }, dataType: json, success: function (res) { // 处理返回数据 }, error: function (xhr, status) { // 处理异常 } });这套写法虽然啰嗦但胜在结构统一、容易理解。后来 ES6 的fetch出来之后很多人觉得原生 API 更简洁但 jQuery 对请求超时、jsonp 跨域、IE 下各种状态码的处理依然是老项目里的兜底保障。1.3 为什么压缩文件这么小却能做这么多事我做过一次测试jquery-3.6.0.min.js 压缩后大概 88KBgzip 后还要再缩到 30KB 左右。对一个能力覆盖面这么广的库来说这个体积控制是很优秀的。秘密在于 min 构建做了三件事去掉注释和空白、缩短局部变量名、合并一些重复的逻辑分支。所以你在压缩文件里看到一堆a、b、c这种变量名并不是开发者故意写谜语而是构建工具干的。还有一个容易忽略的点jQuery 的源码在 IIFE立即执行函数里运行不会污染全局变量唯一的全局入口就是$和jQuery两个名字。这种模块化思路在当时非常超前也为后来require.js时代把 jQuery 作为 AMD 模块加载打下了基础。可以说jquery.min.js 体积小、全局污染少、API 稳定这几个优点叠加起来才让它成为那个时代最值得引用的基础设施。2. 把 jquery.min.js 放进项目版本、CDN 与 $ 冲突2.1 本地下载、CDN、npm三条路分别怎么选引入 jquery.min.js 的方式主要是三种。第一种是把文件下载到本地在 HTML 里写script srcjs/jquery.min.js/script。这种方式的优点是完全可控没有外部网络依赖内网部署、离线环境都能用缺点是版本更新要手动覆盖文件。第二种是走 CDN代码里直接引https://cdn.jsdelivr.net/npm/jquery3.6.0/dist/jquery.min.js这种地址。CDN 的好处是有缓存加速、不用管文件下载适合公网项目坏处是如果用户网络屏蔽了某个 CDN 域名页面就会看到裸奔的 HTML。第三种是 npm 安装npm install jquery然后在 Webpack 或 Vite 项目里import $ from jquery。这里有个关键点npm 包和直接引 script 标签的全局$是两条不同的加载路径如果你在构建项目里用了 npm 包又想在浏览器控制台直接调试$可能拿到的是 undefined因为模块作用域和全局作用域隔离了。想要挂到全局得显式设置window.$ $或者用 ProvidePlugin。2.2 版本选择的老账1.x、2.x、3.x 到底差在哪版本选择是个值得单独说的问题。jQuery 2.x 是在 1.9 基础上删掉对 IE6/7/8 的支持代码更精简jQuery 3.x 进一步转向现代浏览器推荐在不需要兼容老 IE 的时候用。但很多老项目至今还在用 1.12.4就是因为业务上还需要兼容 IE8 时代的浏览器。我见过一个生产事故同事把 jQuery 从 1.11 升级到 3.2结果页面上某个插件还是拿.size()方法做判断而这个方法在 3.x 里已经被移除了用.length替代。升级 jQuery 版本不是换个文件那么简单必须先确认依赖的插件和业务代码有没有用废弃 API。2.3 页面里多个 $ 打架noConflict 怎么救场再讲一个真实高频的痛点页面里同时引入了两个版本的 jQuery或者引入了 Prototype、Zepto 这类同样使用$符号的库后面的库会把前面的$覆盖掉。解决办法是 jQuery 的noConflict()。比如你希望把$让给 Prototype自己用jQuery这个名字访问var $j jQuery.noConflict(); // 之后就用 $j 代替 $ $j(#box).hide();还有一种更常见的情况是同一个页面因为动态插入脚本意外加载了两次 jQuery。这时候不会报错但会浪费加载时间甚至导致事件绑定了两次。可以通过检测window.jQuery是否已经存在来决定要不要再引比如if (!window.jQuery) { document.write(script srcjs/jquery.min.js\/script); }这些细节看着小但在生产环境里任何一个都能让你排查半天。3. 滚动加载、三级联动、iframe 刷新父页面这些场景我直接用 jQuery3.1 滚动到底部加载更多一个很实用的设计jquery 滚动到底部加载更多能成为热搜词说明这是几乎每个后台管理系统都会遇到的场景。思路不复杂监听滚动事件判断滚动条是否接近页面底部接近了就发起 Ajax 请求下一页数据。我写过一个比较稳的版本var page 1; $(window).on(scroll, function () { var scrollTop $(document).scrollTop(); var windowHeight $(window).height(); var docHeight $(document).height(); if (scrollTop windowHeight docHeight - 50) { page; $.get(/api/list?page page, function (res) { if (res.data.length 0) { res.data.forEach(function (item) { $(#list).append(div classitem item.title /div); }); } else { // 没有更多数据了关闭监听避免重复请求 $(window).off(scroll); } }); } });这里有个容易踩的细节判断条件里的- 50是提前量避免每次都到最底才触发。另一个常见坑是滚动事件触发频率太高每滚动 1 像素就发一次请求所以要加一个锁比如用一个isLoading变量请求完成前不响应下一次滚动。更精细的做法是加节流或者用 IntersectionObserver但许多老项目里能用一个布尔锁解决问题就够了。3.2 判断字符串是否包含和保留 2 位小数别小看这两个问题js 判断字符串是否包含看起来基础但引入 jQuery 之后写法就多了。原生方案是str.includes(abc)或者str.indexOf(abc) ! -1。jQuery 里的$.inArray这个方法是用来判断值在不在数组里的不是字符串包含很多新人会在这里混淆。字符串包含就老老实实用原生方法jQuery 没有单独封装一个$.contains给字符串用它只有判断 DOM 节点包含关系的方法这也是一个容易踩的认知坑。至于js 保留 2 位小数(3.1415).toFixed(2)返回的是字符串3.14不是数字这是新手最容易忽略的问题。我在一个报表项目里遇到过合计金额用toFixed(2)之后直接参与下一轮加法结果3.14 5.20变成了3.145.20。正确做法是var sum Number((a b c).toFixed(2));或者更稳妥地用 Math.roundMath.round(num * 100) / 100。Math.round返回数字不会引发类型混淆。浮点数的精度问题是 JavaScript 的固有特性0.1 0.2都不等于0.3所以涉及金额、百分比的数据一定要在算完之后统一处理精度别在中间步骤里反复 toFixed。3.3 三级联动下拉框老业务系统的标准组件三级联动省、市、区在后台管理系统里几乎是必考题jQuery 版本的实现逻辑非常清晰省发生变化时根据省的 ID 请求市级接口市发生变化时再请求区级接口。核心代码可以浓缩成一段$(#province).change(function () { var pid $(this).val(); $(#city).empty().append(option value--请选择--/option); $(#district).empty().append(option value--请选择--/option); $.get(/api/city, { pid: pid }, function (res) { res.forEach(function (item) { $(#city).append(option value item.id item.name /option); }); }); }); // 市级变化时联动区级逻辑同上这个模式的关键是联动源头变化之后下级下拉框要立刻清空否则用户会看到旧数据和当前选择不匹配。另外一个实用细节是回显编辑场景下页面加载时要先稳定省级再用 setTimeout 或者回调触发市级的加载和选择不能直接在页面初始化时同时发三个请求因为省还没选好后面两级的数据根本拉不对。3.4 iframe 关闭之后刷新父页面跨页面通信的一道经典题后台管理页面上经常有打开弹窗或 iframe 编辑一条数据关闭之后父页面列表要刷新。如果用 iframe子页面操作完成之后可以这样通知父页面// 子页面 iframe 内 window.parent.$(#confirmBtn).trigger(refresh); // 父页面 $(#confirmBtn).on(refresh, function () { loadList(); });或者更简单粗暴一点window.parent.location.reload()但这会把父页面整个刷新一遍体验很差一般只适合关闭后必须全量重置的场景。实际情况中还有一个坑iframe 的 src 如果是跨域地址子页面就不能通过window.parent访问父页面的 DOM 和事件这时只能靠postMessage了。jQuery 的 trigger/on 自定义事件在老项目里非常实用能帮你在不引入复杂状态管理的情况下完成协作。从这里就能看出来jQuery 提供的从来不是某一两个 API而是一整套事件系统 DOM 操作 Ajax的配合方式。这种配合逻辑今天看依然好用只是现代前端把这种能力拆成了更细的库或者框架。4. Content-Type、浮点数、Vite 报错jQuery 项目的经典翻车现场4.1 Ajax 的 Content-Type 设置不对后端就是取不到参数jQuery content type 怎么设置能上热搜说明很多人在前后端联调时被它卡住过。jQuery 的$.ajax默认的 Content-Type 是application/x-www-form-urlencoded; charsetUTF-8也就是把 data 里的对象序列化成name张三age18这种格式。如果你的后端接口声明接收 JSON就必须显式设置$.ajax({ url: /api/save, type: POST, contentType: application/json, data: JSON.stringify({ name: 张三, age: 18 }), success: function (res) { } });注意这里data必须用JSON.stringify转成字符串不能直接传对象否则 jQuery 还是会按照表单格式去拼接。我见过一个项目后端用 Spring 的RequestBody接收参数前端漏了contentType设置接口返回 400排查半天才发现是请求头不对。另外一个镜像问题是部分后端框架要求的 Content-Type 是application/json;charsetUTF-8如果你只写了application/json个别老的框架也可能校验失败。最稳妥的方法是用浏览器的 Network 面板看一下实际发出的请求头和后端要求的逐一比对。4.2 Number() 出来的值和原值不一样别让精度问题背锅js number() 和原值不一样这个热搜词其实包含两层情况。第一层是浮点精度问题比如Number(3.14)没问题但0.1 0.2会得到0.30000000000000004。第二层是字符串里夹杂了不可见字符比如从 Excel 复制过来的数字带着\u00a0不换行空格Number( 3.14 )虽然会自动忽略首尾普通空格但遇到特殊空格就会返回 NaN。这种问题在$.ajax拿到后端返回的数据后尤其常见因为 JSON 字符串里的金额字段带了多余的转义或者 BOM 头转成数字就莫名其妙对不上。遇到这种场景我的排查习惯是先console.log(typeof res.amount, JSON.stringify(res.amount))把看不见的字符暴露出来。如果是服务端长度固定补位产生的空格可以.replace(/\s/g, )后再转 Number如果是精度问题就统一用Math.round(Number(str) * 100) / 100收口。别在页面上到处 Number() 三连正确的做法是抽一个formatMoney工具函数统一处理出入口数据。4.3 Promise.all 与 jQuery Deferred 的兼容性Promise.all 是 ES6 的 APIjQuery 的$.ajax返回的是 jqXHR 对象它虽然实现了类似 Promise 的接口.done()、.fail()、.always()但不是严格意义上的 Promise/A 规范对象。所以你不能直接把一个$.ajax返回值塞进Promise.all这在某些浏览器或构建环境下会报错或行为不符合预期。我当时踩过一次Promise.all([$.get(/a), $.get(/b)]).then(...)在 Chrome 里可能能用但在部分老旧浏览器里Promise.all会认为传入的元素必须是真正的 Promise于是报TypeError。如果想在 jQuery 项目里用 Promise.all标准做法是手动包一层function ajaxAsPromise(options) { return new Promise(function (resolve, reject) { $.ajax(options).done(resolve).fail(reject); }); } Promise.all([ajaxAsPromise({ url: /a }), ajaxAsPromise({ url: /b })]).then(function (results) { // 两边都成功后在这里处理 });或者使用$.when$.when($.get(/a), $.get(/b)).done(function (resA, resB) { // 两个请求都完成 });$.when的问题在于参数过多时代码可读性下降而且一旦一个请求失败失败处理也比较繁琐。我把这个转换函数直接放在通用 utils 文件里团队其他人也少踩同样的坑。4.4 Vite 构建时报 transform failedstatic/js/general-9 是什么鬼现在很多老项目被改造时会选择局部引入 Vite 做构建这时就经常遇到报错[vite:esbuild-transpile] transform failed with 2 errors: static/js/general-9。第一次看到这个报错很多人以为文件坏了实际上问题出在 esbuild 默认处理 JS 文件时会按现代标准解析但static/js/general-9这个文件里可能包含了老的脚本语法或者非严格模式下的写法esbuild 解析不通过。我处理过的一个案例是那个 general-9 文件里用了 HTML 注释语法!-- ... --包裹代码这是 IE 时代的老兼容写法esbuild 就不买账。解决方案有几种如果该文件必须保留就在 Vite 配置里把它放到optimizeDeps.exclude或者用vite-plugin-legacy处理如果它只是历史遗留可以直接用原生 JS 重写相关逻辑然后删除这个文件。还有一种情况是文件的 BOM 头问题保存成 UTF-8 无 BOM 之后重新构建就正常了。遇到这种构建报错不要急着改业务代码先把报错文件单独拉出来看编码、看语法、看是否夹杂特殊字符大多数 Transform 问题都出在这三个环节。4.5 验证 URL 有效性正则还是现成 APIjs 验证 url 有效性这个需求在老系统里通常是用一串正则直接匹配。但正则很容易漏掉边界情况比如http://开头没有域名或者域名带了中文。一个更稳的做法是借用浏览器的URL构造函数function isValidUrl(str) { try { new URL(str); return true; } catch (e) { return false; } }这个方法的局限是必须带协议头www.baidu.com会被判为无效。如果业务上允许省略协议可以先自动补上https://再验证。jQuery 的$.ajax里也能通过 URL 有效性提前拦截非法请求避免发出必失败的请求给控制台刷红。这类小工具函数放一起之后项目里的代码会干净不少。5. 从 jquery.min.js 到现代前端面试、迁移与新的选择5.1 音源 js、JS 宏、影视代码里全是 jQuery 的影子最近的热搜词里有一批很有意思lxmusic 音源 js、影视网站代码、JS 宏、野草音源 js。这些名字听起来跟 jQuery 不搭边其实拆开看很多脚本内部就是靠 jQuery 的选择器和事件方法在驱动页面行为。为什么这些人喜欢用 jQuery因为在这些偏用户脚本的场景里没有复杂的工程化工具一个script引入 jquery.min.js然后用$去选择元素、绑定事件、发请求太直接了。哪怕在 2024 年你写一个油猴脚本去增强某个老站点的功能锁定的首选方案仍然是 jQuery它短小、全局可用、面向真实 DOM 编程几乎不需要构建步骤。这也解释了为什么js 宏去掉重复记录、js 影视网站代码这样的关键词能跟 jQuery 同时出现在热搜里。这些领域的作者通常不是工程化背景他们追求的不是框架规范而是最快地把一个页面交互跑起来。jQuery 的学习曲线相对平缓会写 CSS 选择器就会用$这是它至今还活跃在各类脚本社区的根本原因。我并不建议在生产级新项目里继续推 jQuery但在用户脚本、老站增强、内部工具这些场景它依然是性价比很高的选择。5.2 面试常考数组方法、map、函数作用域、异步背后都是同一套 JS吉利 js 面试算法题、js 数组方法、js map、js promise.all、js 异步这些热词说明很多前端面试题表面上是纯 JS 基础其实都在考察对 JavaScript 核心机制的理解。jQuery 时代的开发者容易养成只看效果不看原理的习惯比如知道$.each能遍历数组但不太清楚它跟原生的Array.prototype.forEach有什么区别知道$.ajax能发请求但不理解回调地狱和 Promise 的关系。现在面试喜欢问 map、filter、reduce是因为这些高阶函数能很直接地反映一个人对函数式编程的理解程度。我在面试候选人时不会直接问 jQuery API而是问给你一个对象数组需要拿到其中某个字段去重后的列表你会怎么写。有人写 for 循环有人写Map结构有人用_.uniqBy还有人拿 jQuery 的$.unique来试——这部分就暴露了对工具边界了解多少。学习 jQuery 本身没有错但要在学习过程中理解底层才能在面对现代前端时不被基础题卡住。5.3 从 jQuery 思维迁移到 Vue/TypeScript最需要改掉什么从 jquery.min.js 走到现代前端栈最大的思维转变不是语法而是谁负责更新视图。jQuery 的典型套路是先$(#box)找到节点再手动更新它的内容、样式、属性。Vue 的思路是数据变了视图自动跟着变你根本不需要去操作 DOM。如果一个人写惯了 jQuery刚接触 Vue 时会特别不习惯总想给元素加 id、加 class然后在事件里手动改 innerHTML这种行为被戏称为在 Vue 里写 jQuery。TypeScript 带来的另一个冲击是类型系统。jQuery 代码里的对象结构是隐式的$.ajax返回什么全靠后端文档到了 TypeScript每个接口响应都要定义类型字符串拼接出来的 HTML 模板编译期不会报错但运行时可能缺字段。迁移老项目时我建议按模块渐进式来做先针对 API 响应定义类型再把 DOM 操作密集的模块重构成 Vue 组件最后把全局的$依赖逐步清掉。这个过程急不得我见过一个项目试图两个月把几十万行 jQuery 全部改造成 Vue结果线上事故不断最后回滚了一半。说到底jQuery 并没有消失它只是把统治江湖的位置让给了更契合现代工程体系的框架。但 jquery.min.js 这个压缩文件里封装的解决思路——简化 DOM 操作、统一事件模型、封装异步请求——今天依然在后来的库和框架里延续。作为一个和它打过大量交道的人我的体会是别把它当成过时产物把它当成一门前端历史必修课读懂它再看 Vue、React 的设计会通透很多。最后分享一个小技巧在老项目里调试 jQuery 代码时如果发现控制台输入$返回的不是 jQuery 而是一堆奇怪的东西先检查是不是有别的库抢占了$变量然后执行jQuery.noConflict()并重新用jQuery访问同样的逻辑在概念上适用于任何全局命名冲突的问题。这个排查思路留好了你早晚会用到。本文还有配套的精品资源点击获取
返回列表