写这篇文章前,我想先还原一个场景:刚接触前端那几年,我最常干的一件事就是从收藏夹里翻出 jQuery 的 CDN 链接,复制到页面上,然后开始写$("#id").click(...)。那时候身边的前辈常说一句话:能用 jQuery 解决的问题,绝不用原生 JavaScript 硬刚。这句话放到今天可能有争议,但放在十年前,它确实代表了绝大多数前端开发者的真实心态。做浏览器兼容、快速操作 DOM、发 Ajax 请求,jQuery 几乎是一条龙服务。哪怕到今天 Vue、React 大行其道,很多老项目、模板系统、后端渲染页面里依然跑着 jQuery,搞清楚它是什么、有什么特点、怎么下载和使用,依然是接手这类代码的必备技能。
这篇内容我打算从 JavaScript 库的基本概念讲起,把 jQuery 的核心设计、版本差异、实际使用步骤一条龙拆开,最后再聊一些我在真实项目里踩过的坑。适合刚入门前端、对 jQuery 只有模糊印象的人,也适合那些工作里被迫维护老项目、想快速把基础打牢的同学。
1. 为什么那几年大家都在说"能用 jQuery 解决的问题绝不用原生 JS"
1.1 原生 JavaScript 当年的"原生痛"
2006 年 jQuery 发布那会儿,前端开发远没有今天这么舒服。原生 JavaScript 操作 DOM 的方式不仅冗长,而且兼容性极差。当年中国前端圈子里流传最广的段子是:写十个功能,五个小时是在对付 IE 和 Firefox 的差异。
举个例子,获取元素的方式。老开发者应该都写过这样的代码:
// 老项目的真实写照 var element = document.getElementById("main"); var elements = document.getElementsByClassName("item"); // 如果要用 CSS 选择器找元素?先得自己写个解析器,不然就得靠浏览器抽风式支持这还算好的。到了事件绑定,痛苦才真正开始。IE 下要用attachEvent,标准浏览器用addEventListener,同一个功能你得写两套代码,再加一层判断处理。那时候我们管这叫"浏览器兼容垫片",每写一个功能,先垫一层。
1.2 jQuery 出现在一个什么时代背景
jQuery 就是在这个乱世里杀出来的。它的作者 John Resig 在 2006 年的 BarCamp 上首次发布了这个库。核心目标非常朴素:让 JavaScript 的 DOM 操作在不同浏览器里表现一致,同时把代码量压缩到最小。
jQuery 对当时的开发者来说,几乎等于"解放"两个字。你不再需要记住 IE 和 Firefox 谁用attachEvent、谁用addEventListener,只需要写:
$("#main").click(function() { // 不用管浏览器差异,jQuery 内部帮你处理了 });一个$函数走天下,这一下子把前端开发的门槛拉低了一大截。这也是为什么它的口号是 "Write Less, Do More"——写得更少,做得更多。这句话不是营销话术,是真实的工作量对比。
1.3 这波红利吃到了什么时候
jQuery 的鼎盛时期大概是 2009 到 2015 年。那段时间几乎所有前端框架的底层都借鉴过 jQuery 的思路,很多后端渲染的模板系统也直接内置了 jQuery。后来随着 ES5、ES6 的普及,原生 JavaScript 的能力大涨,querySelector、fetch、addEventListener都变得好用起来,再加上 Vue、React 这类现代框架的崛起,jQuery 的市场份额才开始慢慢下降。
但注意,到现在为止,仍然有不计其数的存量项目依赖 jQuery。你随便打开一个流量比较大的网站,按 F12 输入typeof $,很多时候返回的依然是function。所以学会 jQuery 不是学一门过时的技术,而是掌握一份可能随时要派上用场的资产。
2. JavaScript 库到底是什么,jQuery 帮你把哪些"脏活"干了
2.1 库、框架、插件,先把概念分清
很多人一听到"库"和"框架"就头大,我简单说个区别:库是一堆现成函数的集合,你想用哪个就调哪个,像是你工具箱里一把把不同的螺丝刀,需要的时候抽出来用,主动权在你手里。框架则更像一套装修公司提供的整体方案,你只要按它的规范把家具放进去,房间整体结构是由它决定的。
jQuery 属于前者,是一个极其经典的 JavaScript 库。它把前端开发里高频重复的操作封装成了一组功能:选元素、改样式、绑事件、发请求、做动画、处理表单,全是那些你每天都要写的"体力活"。
2.2 核心封装:jQuery 到底帮你做了什么
下面这张表应该能直观看出 jQuery 的价值,它把原生 JavaScript 里需要用多行、多层兼容代码完成的事,压成了一行。这里我拿最常见的几个场景举例子:
| 场景 | 原生 JavaScript 写法 | jQuery 写法 |
|---|---|---|
| 用 CSS 选择器查找元素 | document.querySelectorAll(".item") | $(".item") |
| 给 10 个同 class 元素绑定点击事件 | 需要 for 循环 + addEventListener,还要处理兼容 | $(".item").click(fn),自动遍历绑定 |
| 修改一组元素的文本 | 循环遍历逐个改 | $(".item").text("新内容") |
| 发送异步请求 | XMLHttpRequest+ 状态判断 + 各种兼容处理 | $.ajax({ url, method, success }) |
| 页面加载完成后执行 | 判断DOMContentLoaded,还得处理差异 | $(function(){}) |
关键在于,jQuery 不是简单地把原生 API 换个名字,而是内部做了大量跨浏览器兼容处理。比如$.ajax在 jQuery 内部已经帮你处理了 IE 的ActiveXObject、标准浏览器的XMLHttpRequest、各种状态码判断、超时处理等。你不用知道底层有多少种实现,只要知道调$.ajax就行。
2.3 一句话理解 jQuery 的核心模型
如果你只用一句话去理解 jQuery,可以这么说:jQuery 把所有操作都挂在一个$函数和它返回的对象上。不管你是查元素、建对象、还是取上下文,$是你的总入口。$返回的不是原生的 DOM 元素,而是一个 jQuery 对象,这个对象内部包裹了 DOM 元素,同时挂载了 jQuery 的整套操作方法。
这里有个容易踩的坑:$("#id")和document.getElementById("id")返回的东西不一样。前者返回 jQuery 对象,你能直接.css()、.click()、.html(),但不能直接调用原生 DOM 的方法,比如getAttribute在某些老版本里可能不适合直接调用。反过来,如果你拿了一个原生 DOM 对象,想用 jQuery 的方法,就得包一层:$(domElement)。这个转换逻辑我在后面会重点讲。
3. jQuery 的三大核心设计支柱:选择器、链式调用、隐式迭代
3.1 选择器引擎:为什么$("#id")能火这么多年
jQuery 最出名的就是它的选择器。它支持你用几乎所有的 CSS 选择器语法来查找元素,从最基础的#id、.class,到属性选择器[type="text"]、子元素选择器ul > li,再到伪类选择器:first-child,全部可以直接用。
我在实际项目里最常用的是组合查找,比如:
$("ul.nav > li.active"); // 层级 + class,一步到位 $("input[name='username']"); // 表单元素按 name 属性选,比遍历简单太多 $("tr:even"); // 选偶数行,做表格条纹效果一行搞定换句话说,别人用原生 JavaScript 写一行循环再写一行判断才能找到的元素,你用 jQuery 一条字符串就表达完了。它的底层实现其实是封装了一套选择器解析引擎,早期用正则 + 遍历 DOM 的方式实现,后期在某些版本里也利用了原生querySelectorAll加速。但对你来说,这些都不用管,记住"能用 CSS 选择器描述的,jQuery 都能查到"就够了。
另外很多人会自然想到一个问题:我直接用document.querySelectorAll不行吗?现在当然可以。但 jQuery 的$返回的是一个封装好的 jQuery 对象,上面绑定了全套操作方法,这是querySelectorAll返回的静态 NodeList 做不到的。选择器只是入口,真正的价值在入口之后那一整套方法。
3.2 链式调用:代码为什么能一行接一行地写
jQuery 另一个标志性设计是链式调用。看一段典型的 jQuery 代码:
$("#box") .css("background", "#f5f5f5") .attr("data-loaded", "true") .click(function() { alert("clicked"); });这段代码能一行接一行写下去,是因为 jQuery 里几乎所有方法(除了取值类的)执行完后都会返回同一个 jQuery 对象。css()设置完样式,返回对象;attr()设置完属性,返回对象;click()绑定完事件,还是返回对象。于是一个接一个地"串"起来,像流水线一样。
这个设计哲学背后的逻辑是:你要操作一个元素时,通常不是只做一件事。改完样式可能还要加属性,加完属性可能还要绑事件。如果每一步都重新查一次 DOM,效率低代码也啰嗦。链式调用让你在一条语句里完成一个完整的操作链,代码可读性反而更高。
用一个生活化的例子来理解:你去银行办事,窗口 A 办完业务盖个章,直接送窗口 B,窗口 B 再送窗口 C,一气呵成。每个窗口都接收上一个窗口的结果并继续处理,最后你到终点时所有事情都办完了。链式调用就是这样。
但这里要记住一个顺序问题:取值类方法会中断链式调用。比如你要拿$("#box").html(),返回的是一个字符串,自然不能再继续.css()。所以一般做法是:要么把取值放在链条最后,要么先用变量存起来再继续新操作。
3.3 隐式迭代:为什么你写 jQuery 几乎不用 for 循环
原生 JavaScript 里给一组元素绑定事件,你得写:
var items = document.querySelectorAll(".item"); for (var i = 0; i < items.length; i++) { items[i].addEventListener("click", handler); }jQuery 里呢?只有一行:
$(".item").click(handler);这就是隐式迭代。jQuery 在内部自动帮你遍历了所有匹配到的元素,并把后面的操作应用到每一个元素身上。你不需要关心选择器匹配到一个还是十个元素,直接写方法就行。
这个设计极大减少了 for 循环的使用频率。早期前端代码动辄三层嵌套循环,用 jQuery 之后很多循环都"隐形"了。但注意,隐式迭代也有副作用:当你用$(".item").css("color", "red")时,它会把匹配到的所有元素都改成红色,而不是只改第一个。如果你只想改第一个,要用.eq(0)或者:first。我第一次接触 jQuery 时就在这里翻过车:想只改第一行样式,结果整组的样式都被改了。
3.4$(document).ready()到底在等什么
几乎每个 jQuery 教程都会教你写这段入口代码:
$(document).ready(function() { // 页面 DOM 加载完成后执行 });它和我之前提到的$(function(){})是同一个意思,等价于原生 JavaScript 里的DOMContentLoaded事件。它表达的是"DOM 结构已经准备好了,但图片、样式这些资源文件可能还没加载完"这个时机。
这个设计的好处是,你不用像最古老的做法那样把脚本放在</body>前面才能安全操作 DOM,也不用挂window.onload等所有资源都加载完。jQuery 会自己判断时机,脚本放哪里都可以。当然现在你用现代 JavaScript 的DOMContentLoaded也能实现同样效果,但 jQuery 这个写法历史更早,很多老代码里你都会见到它。
4. 版本选择与下载:不只是打开官网点下载那么简单
4.1 版本分支之间的区别到底怎么选
你要真去 jquery.com 下载页,会发现它提供多个版本,最容易看晕的是 1.x、2.x、3.x 三个大版本。这里我直接说结论,方便你在项目里快速决策:
| 大版本 | 特性 | 适用场景 |
|---|---|---|
| 1.x | 支持 IE6/7/8,体积相对大,兼容做得多 | 老项目、政府或银行系统的历史页面 |
| 2.x | 放弃 IE6/7/8 支持,体积更小,性能更好 | 当年过渡期用的多版本,现在不太推荐新项目选 |
| 3.x | 当前主线版本,支持现代浏览器,API 基本平稳,体积最优,修复了大量老问题 | 新项目、维护多年的项目升级首选 |
简单说,如果你不是特殊行业、没有老 IE 兼容需求,直接选最新的 3.x 版本就行。哪怕你需要兼容老项目,也应该先在 3.x 里找解决方案,实在不行再考虑降级到 1.12 左右的经典版本。千万不要一上来就下载 2.x,那个版本比较鸡肋,弃了老 IE 又不彻底,现在看有点两头不讨好。
另外每个大版本内部还分两个版本线,名字听起来很相似:
- 压缩版(minified):文件名里带
.min,去掉所有空格注释,变量名缩短,文件体积小,生产环境用。 - 未压缩版(uncompressed):完整可读代码,适合学习源码、调试问题,开发环境用。
我在本地调试时习惯直接用未压缩版,方便 F12 里看报错堆栈;上线前再切换成.min版本。如果你忘了切,影响也不大,但加载体积和解析速度确实不如压缩版理想。
4.2 CDN 引入:为什么我推荐这种下载方式
下载 jQuery 不只有"保存文件到本地"这一种方式。更常见的做法是直接使用 CDN(内容分发网络)上的公共资源。原理是你本地页面里写一个<script>标签,浏览器直接从一个遍布全国的节点服务器加载jquery.min.js文件。
推荐的 CDN 引入方式大概长这样:
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>CDN 方式最大的好处有三个:加载快,因为文件存在离用户更近的节点;减少服务器压力,流量不经过你自己的服务器;版本切换方便,同一个链接改下版本号就能换。当然它也有安全隐患,如果 CDN 域名有波动,页面就加载不到 jQuery。所以国内外大型项目一般会做一个双保险:优先 CDN,失败后用本地文件兜底。
这里提醒一句,如果你做的是企业内部系统,内网环境无法访问外网 CDN,那就老老实实把jquery.min.js下载下来放到自己服务器的静态目录里。这种情况下没有别的办法,本地文件是唯一靠谱的选择。
4.3 本地手动下载应该放在哪个目录
如果你的项目是传统后端渲染模式(比如 Java 的 JSP、Python 的模板、PHP 直出页面),通常会有一个默认的静态文件目录。下载完的 jQuery 文件建议放成这样:
project-root/ ├── static/ │ ├── js/ │ │ ├── lib/ │ │ │ └── jquery-3.7.1.min.js │ │ └── app/ │ │ └── main.js │ └── css/ │ └── style.css注意lib和app分开是个好习惯。第三方库文件和自己的业务脚本混在一起,后期升级版本容易误碰误改。我曾经在一个老项目里见过有人把 jQuery 源码改了,就为了解决一个 bug——这种做法后期维护成本极高,强烈不建议。
4.4 引入之后怎么确认 jQuery 真的加载成功了
最直接的验证方式是打开浏览器控制台,输入:
typeof $如果返回"function",说明$是可用的;如果返回"undefined",说明 jQuery 没有加载成功,或者次序不对。还有一个更常见的坑是引入顺序问题:<script src="jquery.min.js">必须出现在所有依赖 jQuery 的业务脚本之前。比如你写了main.js,里面用到$,但你把main.js引在 jQuery 前面,浏览器执行到main.js时$还不存在,直接报$ is not defined。
我见过很多新手在这个问题上卡住,排查步骤其实很简单:先确认 jQuery 文件是否在网络请求里出现(F12 的 Network 面板看状态码是不是 200),再确认 jQuery 标签是否在业务脚本之前。这两点检查完,绝大部分引入问题都能解决。
4.5 一个经常被忽略的坑:$被别的函数占了怎么办
jQuery 把全局入口简写成了$,但这跟其他库可能撞车。比如你的页面同时引用了 LazyLoad、其他工具库,它们也可能声明了$。还遇到过一些团队在全局命名里自定义了$,结果 jQuery 加载后被覆盖掉。
jQuery 提供了$.noConflict()方法解决这个冲突。最常见用法是:
var jQuery2 = jQuery.noConflict(); // 之后用 jQuery2 代替 $,比如 jQuery2("#main").click(...)它会让 jQuery 释放$这个全局变量,但保留jQuery这个名字。这样其他库继续用它们的$,你这边用jQuery或jQuery2调用 jQuery。
实际项目里,我更推荐的方案是:引入阶段直接用jQuery全名,而不依赖$。尤其写公共组件、插件这种可能被第三方环境复用的代码,用全名能减少命名冲突。别小看这个问题,在我印象里至少有两次发布事故都跟$被覆盖有关。
5. 从零跑通第一个 jQuery 页面:核心 API 使用实操
5.1 一个最小但完整的 HTML 骨架
现在假设我们要做一个最简单的待办列表页面:一个输入框,一个添加按钮,一个列表,点击按钮把输入框的内容追加到列表里。这算是 jQuery 入门里的"Hello World",但比单纯打印字符串有信息量得多。
先把页面骨架搭出来:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>jQuery 入门示例</title> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script> </head> <body> <div id="app"> <input type="text" id="todo-input" placeholder="输入待办事项"> <button id="add-btn">添加</button> <ul id="todo-list"></ul> </div> <script src="js/app.js"></script> </body> </html>页面思路很简单:#app是容器,#todo-input是输入框,#add-btn是按钮,#todo-list是待办列表容器。注意引入顺序,jQuery 放在了最前面,业务脚本app.js放在最后面。这样做可以保证业务脚本执行时 jQuery 已经就位。
5.2 入口函数与事件绑定的第一次接触
打开app.js写代码:
$(function() { // 等待 DOM 就绪后执行内部代码 $("#add-btn").on("click", function() { var val = $("#todo-input").val(); if (val === "") { alert("请输入内容"); return; } var item = $("<li>").text(val); $("#todo-list").append(item); $("#todo-input").val(""); }); });逐行拆解一下:
$(function(){})是页面 DOM 加载完成后再执行的入口,防止脚本在 DOM 尚未生成时去查元素,查到空对象。$("#add-btn").on("click", handler)给按钮绑定点击事件。这里用了完整写法.on(),它比.click()方法更值得推荐,因为你后期如果要移除事件、绑多个同类事件、或者动态替换元素,.on()的灵活性明显更强。$("#todo-input").val()是取值方法,获取输入框的当前值。.val()不带参数时是获取,带参数时是设置,这也是 jQuery 方法重载的通用套路。$("<li>")直接用 HTML 字符串创建了一个全新的 jQuery 对象,它还没进 DOM,类似一个"待上架的商品"。然后.text(val)设置它的文本内容。我刻意用了.text()而不是.html(),因为如果内容是用户输入的,.text()会把它当纯文本处理,能避免用户输入<script>这类字符导致的问题。$("#todo-list").append(item)把创建好的li追加到列表末尾。- 最后
$("#todo-input").val("")清空输入框。
这就是一个完整的、可以跑通的交互闭环:取值、判断、创建元素、插入 DOM、清理状态。你会发现全程没有一次 for 循环,也没有一次变量缓存 DOM 节点,代码非常紧凑。
5.3 动态添加的元素为什么点击没反应
很多新手照着上面的代码继续扩展,会遇到一个经典问题:某个列表项是后来动态创建的,给它的点击事件怎么绑都不生效。比如你会这样写:
$("#todo-list li").on("click", function() { // 想点击某一项后对它做点什么 });问题是这个代码在页面加载时执行,而#todo-list里此刻还没有任何li,所以$("#todo-list li")选出来的是空集合,事件自然绑不上去。后面你动态追加的li是后来才出现的新元素,绑定动作早都已经完成了。
解决办法是用事件委托:
$("#todo-list").on("click", "li", function() { var text = $(this).text(); alert("点击了:" + text); });这里.on()的第二个参数"li"是关键。它表示把事件绑定在#todo-list容器上,当任何li上发生 click 事件时,事件会冒泡到容器上被捕获处理。这就是事件委托。好处是无论li是什么时候出现的,哪怕是后续动态添加,只要它在容器内点击,就能触发回调。这是 jQuery 实际开发里最重要的套路之一,处理动态列表、表格、菜单时极为常用。
事件委托是 jQuery 老开发者离不开的工具,同时也是最容易遗忘的知识点。为了这个坑,我曾经在一个后台管理表格里排了整整一个晚上的 bug——新添加的行没有响应点击,后来就是靠事件委托解决的。
5.4 数据属性与样式切换:表格斑马纹与状态管理
实际项目里你会频繁遇到"根据数据状态修改样式"的需求。jQuery 处理起来很直接。比如待办列表里,我们希望点击一项后把它标记为"已完成",字体变灰色并加删除线:
$("#todo-list").on("click", "li", function() { $(this).toggleClass("done"); });配合 CSS:
.done { color: #aaa; text-decoration: line-through; }toggleClass会在元素有done类时移除,没有时添加。这个方法的语义非常清楚,是切换状态的最佳选择。如果你要强制加上某个类,用addClass;强制移除,用removeClass。
另一个常见需求是对带数据的元素操作:比如把待办事项的 ID 存在data属性里,点击时读取:
var item = $("<li>").data("id", 123).text(val); $("#todo-list").append(item); // 后续点击读取 $(this).data("id");.data()方法在 jQuery 里做得比较巧妙,它会自动做类型转换,存进去的"123"取出来可能直接就是数字123。如果你用 HTML 标签的>$.ajax({ url: "/api/todos", method: "GET", dataType: "json", success: function(res) { // 请求成功回调 renderTodoList(res.data); }, error: function(xhr, status, error) { console.error("请求失败", status, error); } });
如果你只需要请求成功,可以用简写:
$.get("/api/todos", function(res) { renderTodoList(res.data); });$.get默认就把响应内容传回回调函数,省去写完整$.ajax配置的麻烦。不过我做项目时更偏好用完整的$.ajax写法,因为可以显式设置dataType、超时、错误处理,结构清晰。
要特别注意,jQuery 的success、error是回调函数,跟现代 JavaScript 的Promise是两套体系。如果你在项目里用 jQuery 发请求,想链式调用.then(),部分新版 jQuery 也支持 Deferred 对象,但语义跟原生Promise还是有差异,建议维护老代码时别混用,容易绕晕。
6. 实际项目里的 jQuery 使用注意事项与维护经验
6.1 能用.on()就少用.click()和.bind()
很多老代码里有.click()、.bind()、.live()、.delegate()这几个方法。如果你是从老项目起步,大概率会见到它们。这里直接给你结论:
.click()是.on("click", ...)的简写,功能差不多,但如果你需要绑定多个事件或做委托,简写不够灵活。.bind()是 jQuery 早期的事件绑定方式,只能给当前已存在的元素绑定事件。.live()是基于冒泡的事件委托,但局限在document上,性能有瓶颈,早就被废弃。.delegate()是比较早的事件委托方法,功能上没问题,但现在统一用.on(events, selector, handler)写法更好。
我的建议很简单:新代码里一律用.on()。它在功能上覆盖了其他几个方法的场景,语义也统一。维护老代码时,看到一个.live()或.delegate(),可以顺手迁移到.on(),减少兼容隐患。
6.2 注意 jQuery 对象内存泄漏的隐患
jQuery 使用久了会有一个很容易忽视的问题:动态创建的元素如果只从页面上移除,但没有清理绑定的事件或数据,可能会在内存里留下引用。尤其是 SPA 场景下频繁创建销毁 DOM,内存就会悄悄涨上去。
常规做法是,删除元素前先解绑事件:
// 先解绑事件再移除 $("#todo-list li").off("click").remove();更细致一点,如果你用了自定义数据存储.data(),删除前也需要考虑清理。虽然现代浏览器和框架能自动回收大多数情况,但维护老项目或者你做的是长时间运行的页面(比如后台管理系统),这点还是值得留意的。
6.3 全局命名空间污染与封装思路
jQuery 本身是全局的,你写的所有业务代码如果不做任何封装,都会污染window。老项目里常见的是各种全局函数满天飞,function a(){}、function b(){}写满整个 JS 文件,后来越写越乱,排查时根本不知道哪个函数是哪个模块的。
我后来养成的习惯是:所有页面级代码都包在一个立即执行函数里,或者定义一个页面级命名空间对象:
var TodoApp = { init: function() { this.bindEvents(); this.loadList(); }, bindEvents: function() { $("#add-btn").on("click", this.addItem.bind(this)); }, addItem: function() { // 具体逻辑 }, loadList: function() { // 拉取数据 } }; $(function() { TodoApp.init(); });这种方式在今天的框架思维里看不算多高级,但在纯 jQuery 项目里,它已经能极大提升可维护性。至少你不会在三个月后打开代码时,看着一堆裸全局函数发懵。
6.4 和现代框架混用的边界
很多团队在逐步把老项目往 Vue 或 React 迁移,迁移过程中不可避免会有一部分页面还在用 jQuery。这时候最容易出现的坑是两套体系互相操作同一块 DOM,造成状态不同步。
我的经验是:划清边界。如果这个页面用 Vue 管理数据绑定,那 jQuery 尽量只做非常外围的事情,比如初始化某个第三方插件、处理某个独立小模块。反过来,如果你在一个仍然由 jQuery 主导的页面里临时用了 Vue,也别让 Vue 去管已经被 jQuery 频繁修改的 DOM 区域,否则会出现"改了 jQuery 那边不更新,更新了又被覆盖"的诡异问题。
这个边界问题比技术问题更难解决,因为它考验的是工程判断力。我的建议是,在 HTML 结构上做好物理隔离:jQuery 负责的区域和框架负责的区域从根节点上分开,互不交叉,这是最稳妥的。
6.5 不要为了用 jQuery 而用 jQuery
最后说点掏心窝的。不少刚开始学前端的人会有一个误区,觉得学会 jQuery 就能解决一切前端问题。实际上 jQuery 最擅长的是快速操作 DOM、绑定事件、处理简单的异步逻辑。如果你的项目里大量用到复杂状态管理、组件复用、数据驱动视图,那么更可能适合现代框架,硬生生用 jQuery 也能实现,但代码会变得极其难维护。
我实际遇到过一个极端案例:有人用 jQuery 硬写了一个完整的 SPA,所有页面切换全靠手动维护路由、手动渲染 DOM、手动管理状态,最后代码两万多行,改一个功能要牵一发动全身。不是说 jQuery 做不到,而是它的设计目标本来就不是干这个的。
所以在选型时,我建议多问自己一句:我到底要做什么?只是给一个后端渲染页面加点交互,jQuery 是最顺手的选择之一;要做一个完整的单页应用,那就别纠结,去学现代前端框架,jQuery 可以在你写插件或维护旧代码时作为一份额外技能存在。
用 jQuery 十来年,说不上它有多"先进",但它确实用一种朴素的方式,让一代前端开发者的工作效率得到了实打实的提升。每次接手老项目,看到那些熟悉的$("#xxx").on("click", ...)代码,都会觉得像翻开一本旧相册——虽然相册里都是老照片,但每一张都记录着真实发生过的事情。如果你也正在跟这类代码打交道,希望这篇笔记能帮你少踩几个坑,把那堆"老代码"伺候得明明白白。