
先说点实际的现在前端圈子一聊到 jQuery很多人第一反应是“都什么年代了还在用这玩意儿”。这话对但也不全对。你去翻翻那些老项目、后台管理系统、跨境电商后台、各种插件源码jQuery 依然活得好好的。很多公司的新项目确实上了 React 或 Vue但遗留系统里那一大堆$开头的代码总得有人能看懂、能维护、能改。而且说句公道话jQuery 那套“选择器 链式调用 隐式遍历”的设计思路直到今天依然影响着无数前端框架的 API 设计。所以这篇文章不整虚的直接把 jQuery 最核心的知识点拆开揉碎讲清楚覆盖选择器、DOM 操作、事件绑定、Ajax 封装、动画队列、工具函数这些日常开发里真正高频使用的部分。不管你是刚入门前端的新手还是被临时拉去维护老项目的“接盘侠”只要把这些核心点吃透看别人代码不再费劲自己写起来也能又快又稳。1. 先搞清楚 jQuery 的定位与核心机制1.1 为什么我们还需要懂 jQuery先说个最简单的问题为什么 jQuery 当年能横扫整个前端圈除了赶上了好时候更关键的是它解决了真实痛点——浏览器兼容性。在 IE6 还在蹦跶的年代要拿到一个元素、绑定一个事件、改一下样式每个浏览器写法都不一样开发者被折磨得死去活来。jQuery 的做法是你只需要写一套代码底层兼容逻辑它帮你处理好了。这个思路放在今天也不过时。从工程化的角度看jQuery 虽然现在不再是主流框架但它的设计思想里有三个东西非常值得记住选择器引擎基于 CSS 语法扩展出的选择器体系让“找元素”这件事变得极其自然。链式调用每个方法操作完元素后返回 jQuery 对象本身于是可以.addClass().attr().appendTo()无限往下串。隐式迭代你用选择器选出来的是元素集合但调用.text()、.css()这类方法时jQuery 会自动帮你遍历集合中的每个元素去执行操作不需要你写 for 循环。这三个机制就是 jQuery 的灵魂。后面所有知识点本质上都是围绕这三件事展开的。理解了它们你再看任何一段 jQuery 代码脑子里都能自动“解码”。1.2$到底是什么它和 DOM 有什么区别$也就是jQuery本质上是一个函数。你调用$(selector)的时候它会返回一个 jQuery 对象这个对象里面封装了匹配到的 DOM 元素并且带了一堆操作这些元素的方法。这里必须强调一个高频易混淆点jQuery 对象和原生 DOM 对象不是一回事。比如let $div $(#app); // jQuery 对象 let div document.getElementById(app); // 原生 DOM 对象$div.text(hello)可以正常执行div.text(hello)会直接报错因为原生 DOM 没有.text()这个方法。反过来的情况也一样。$div.innerText得到的是undefined因为 jQuery 对象不是普通对象它是一个类数组结构真正的 DOM 元素藏在里面。所以实际开发中经常需要互相转换jQuery 对象转原生 DOM$div[0]或者用.get(0)。原生 DOM 转 jQuery 对象$(div)。我自己的习惯是如果在一个函数里已经拿到了原生 DOM后续操作又比较杂直接$(that)包一层省得来回倒腾。这种“包一层”的用法也是 jQuery 里非常有特色的设计后面还会用到。2. 选择器找元素这件事其实有讲究2.1 基础选择器与层级选择器速查jQuery 选择器覆盖了大部分 CSS 选择器并且还扩展了一堆自己的东西。平时最常用的整理如下类型写法示例作用ID 选择器#id$(#user-list)按 ID 精确查找类选择器.class$(.item)按 class 查找标签选择器tag$(li)按标签名查找后代选择器A B$(ul li)找 A 内部所有的 B子元素选择器A B$(ul li)只找 A 的直接子元素 B相邻兄弟选择器A B$(h2 p)找紧跟在 A 后面的第一个 B通用兄弟选择器A ~ B$(h2 ~ p)找 A 后面所有同级的 B这里说一个容易忽视的点$(ul li)和$(ul li)的区别。前者找的是 ul 里面所有层级的 li包括嵌套子列表里的后者只找 ul 直接子层级的 li。我在处理多层菜单结构时经常用来精准控制避免误操作内层元素。如果你拿不准层级关系实际调试时可以先用console.log($(...).length)看看匹配了多少个元素再决定要不要调整选择器。2.2 过滤选择器:first-child、:first、:eq到底怎么选这个点非常高频因为太容易踩坑了。先看一段典型需求在一个列表里想选中第一个子元素。ul idlist li第一项/li li第二项/li li第三项/li /ul三种常见写法$(#list li:first-child) // 如果 li 是其父元素下的第一个子元素则匹配 $(#list li:first) // 匹配第一个 li $(#list li:eq(0)) // 匹配索引为0的 li:first-child是 CSS 选择器它的判断依据是“这个元素是不是父元素下的第一个子元素”根本不关心你是不是 jQuery 对象集合里的第一个。如果 ul 里第一个子元素不是 li 而是 span那li:first-child什么都匹配不到。:first是 jQuery 扩展直接取匹配集合里的第一个元素。:eq(0)更直观按索引取值:eq(1)就是第二个:lt(2)是前两个:gt(0)是除第一个以外全部。我自己最常用的是:eq和:first因为可读性好而且不会因为 HTML 结构里多了个无关标签就出问题。但如果你需要严格的“父元素下第一个子元素”这种语义那就必须用:first-child。补充一个冷门但很实用的点:first-child是逐父元素匹配的。也就是说$(li:first-child)会选到页面上所有作为父元素第一个子元素的 li不是只选一个。想要全局限定务必结合上下文比如$(#list li:first-child)。2.3 根据 name 属性获取对象热搜词里出现了“jquery 根据 name 获取对象”这个在工作中确实常用。尤其是处理表单的时候很多后端接口的字段名是对应 input 的 name 的你用 id 还得一个个查直接用 name 选就顺理成章了。// 获取 name 为 username 的元素可能是 input也可能是其他标签 let $input $([nameusername]); // 精确获取具体类型的元素比如文本框 let $textInput $(input[nameusername]); // 获取同一 name 的一组元素典型场景是 radio 单选组 let $radioGroup $(input[namegender]);注意一个细节$([nameusername])里的属性值建议用引号包起来。虽然某些情况下不写引号也能跑比如$([nameusername])但只要属性值里出现特殊字符比如点、冒号、中括号不写引号就可能直接报错或者匹配不到。为了省心一律加引号。还有另一个容易踩的坑name属性在 IE 老版本下对某些元素比如动态创建的 input可能表现不一致不过现在基本不用考虑这个问题了。倒是有一个很实际的小坑如果你用$(document.getElementsByName(...))这种方式混用得到的可能不是预期结果因为getElementsByName返回的是一个 NodeList直接用$()包一层是可以的但更建议通过属性选择器一步到位。2.4 表单选择器与内容过滤选择器做表单交互的时候jQuery 也提供了一些固定的简写选择器:input匹配所有 input、textarea、select、button注意不仅限于input标签。:text、:password、:radio、:checkbox、:file对应不同类型的 input。:checked匹配所有被选中的 radio、checkbox、option。:selected匹配被选中的 option。:visible、:hidden根据元素是否可见来匹配。注意display: none和visibility: hidden在 jQuery 的可见性判断里结果不一样前者算隐藏后者算可见因为它们仍然占据页面空间。:contains(text)匹配内容包含指定文本的元素。比如$(p:contains(警告))但要注意它匹配的是“包含”不是“完全等于”而且它会把后代元素的文本也算进去。想精确匹配建议还是用filter()方法。:has(selector)匹配包含某个子元素的选择器。比如$(div:has(.title))就是找所有内部含有.title的 div。这些选择器用好了能极大简化代码。比如全选/反选功能一行$(input[nameids]:checked)直接拿到所有被选中的项完全不需要自己循环判断。3. DOM 操作与链式调用的实战细节3.1 创建元素与插入元素的多种姿势用 jQuery 创建新元素非常方便核心就是$(li文本/li)。你可以直接把完整的 HTML 字符串传进去jQuery 会把它解析成 DOM 节点并包装成 jQuery 对象。这个操作看起来简单但有几个细节需要注意// 创建元素并设置属性 let $newItem $(li, { text: 新条目, class: item-active, data-id: 123, click: function() { console.log(点击了); } }); // 插入到 ul 末尾 $(#list).append($newItem); // 换个姿势在 ul 开头插入 $(#list).prepend($newItem.clone()); // 注意不要直接用同一个对象否则会移动位置一个常见的误解是同一个 jQuery 对象只能在一个位置存在。如果你把$newItem先 append 到了某个容器然后又 append 到另一个容器它并不会复制一份而是会“移动”过去。所以需要同时出现在多个地方时必须用.clone()。插入相关的方法一共有六组容易记混我列个表方法含义append(content)在元素内部的末尾插入prepend(content)在元素内部的开头插入after(content)在元素外部、紧挨着元素的后面插入before(content)在元素外部、紧挨着元素的前面插入appendTo(target)把当前元素追加到目标元素内部末尾insertAfter(target)把当前元素插入到目标元素后面append和appendTo的区别纯粹是语法糖方向不同A.append(B)等价于B.appendTo(A)。选择哪种写法取决于句子读起来顺不顺。比如你想表达“把这个图标插到按钮后面”$icon.insertAfter($btn)就比$btn.after($icon)更符合直觉。3.2 删除、清空与替换.remove()把元素从 DOM 中移除同时移除它上面绑定的事件和$.data缓存数据。如果只是临时拿掉后面还要重新加回去就不该用 remove而应该用 detach。.detach()移除 DOM 但保留事件和数据。这个方法是早期做“选项卡切换”“局部刷新恢复状态”时的利器。比如一个表格行上有复杂的点击事件你先把整个表格.detach()下来改完数据再append回去所有事件都还在不需要重新绑定。.empty()清空元素的内部内容保留元素本身。.replaceWith(newContent)用新内容替换当前元素。注意替换后原来的元素没了它上面的事件也没了如果需要保留事件要么用detach提前存好要么用事件委托绕开这个限制。这里分享一个我自己的实践经验在处理动态列表的“删除行”功能时第一时间想到的往往是$(this).closest(tr).remove()但如果你在行里面有多个事件绑定直接 remove 会把一起带走下次新建的行又没有事件了。更稳的做法是事件绑定统一交给列表容器用事件委托来做后面会细讲这样不管你删了多少行新加的行天然就具备事件能力完全不需要重复绑定。3.3 属性、CSS 与 class 操作这些坑你迟早会踩属性操作有三个容易混淆的方法.attr(name, value)操作 HTML 属性比如id、name、href、>// 正确设置选中状态 $(#agree).prop(checked, true); // 正确读取选中状态 if ($(#agree).prop(checked)) { // do something }CSS 类操作就简单多了$(#box).addClass(active); $(#box).removeClass(active); $(#box).toggleClass(active); $(#box).hasClass(active); // 返回布尔值还有一个很实用的.css()方法可以批量设置样式也支持传对象$(#box).css({ background-color: #f5f5f5, font-size: 14px, border-radius: 4px });需要注意.css()设置样式是写在行内style 属性里的优先级比外部样式表的类要高。所以如果你用.css()改了样式后面想用.removeClass()恢复成类定义的样式是恢复不了的必须显式.css(background-color, )把行内样式清掉才会重新应用样式表里的值。3.4 链式调用的底层逻辑与“污染”问题链式调用确实好用但新手很容易忽略一个事实链式调用的前提是每一步操作返回的都是一个新的 jQuery 对象。大部分操作方法返回的都是“当前集合的副本”或者“操作后的集合”但也有一些方法会改变集合本身。这里有一个著名的坑.end()方法和毁掉链的行为。举例选中ul然后找到里面的li操作完想回到ul继续操作可以用$(#list) .find(li) .addClass(highlight) .end() // 回到 #list .addClass(bordered);但如果你中间用的是.eq(0)、.first()、.filter()这类“过滤”操作这些方法会改变集合再调.end()时回到的是过滤之前的集合。简单记.end()只能回退最近一次“查找/过滤”之前的状态。另外一个更隐蔽的问题有些方法返回的集合和预期不一致。比如.children()返回的是子元素集合你用.children().addClass(x)操作的是子元素不是当前元素。这时候想回到当前元素同样需要.end()。在实际开发里链式调用一旦超过 5 步我建议还是拆成变量存一下对可读性和 debug 体验都友好很多。硬秀链式技巧后面自己回来看代码都容易懵。4. 事件机制绑定、委托与命名空间4.1 事件绑定的演进从 bind 到 on早期 jQuery 版本常用的.bind()、.live()、.delegate()现在基本退出了历史舞台。从 jQuery 1.7 开始官方推荐统一用.on()而.off()对应解绑。这个设计其实很聪明.on()一个方法同时实现了“直接绑定”和“事件委托”两种模式。基础用法$(#saveBtn).on(click, function() { let $form $(#mainForm); // 表单校验逻辑 if (!$form.valid()) { return; } // 提交 }); // 多个事件绑定同一个处理函数 $(#input).on(keyup change, function() { updatePreview(); });解绑的时候用.off()$(#saveBtn).off(click); // 移除所有 click 事件 $(#saveBtn).off(click, handlerFn); // 只移除指定的处理函数.off()移除的必须是同一个函数引用所以如果你用匿名函数绑定后面想精确解绑就麻烦了。我自己习惯在需要解绑的场景下把处理函数抽成具名函数这也是一个值得坚持的好习惯。4.2 事件委托动态元素的事件绑定方案动态添加的元素直接用.on(click, handler)是绑不上事件的因为它根本不存在。这个问题在 jQuery 时代就是通过事件委托解决的在 React/Vue 时代其实也用类似的思想事件冒泡到容器。// 错误示范直接绑定动态加的行没有事件 $(.row .delete-btn).on(click, function() { ... }); // 正确示范绑定到稳定的父容器通过第二个参数过滤 $(#table tbody).on(click, .delete-btn, function() { let $row $(this).closest(tr); // 这里的 this 是实际点击的 .delete-btn不是 tbody });这个写法的关键在于事件是挂在tbody上的不管tbody里的.delete-btn是本来就有的还是后来动态加的点击时都会实打实地冒泡到tbody然后 jQuery 会判断触发元素是否匹配第二个参数的选择器匹配就执行函数。这样好处很明显动态元素天然支持事件不需要重新绑定页面上事件数量更少内存占用更低批量删除、行内编辑这种高频交互很好维护。代价也不是没有如果容器层级嵌套太深事件冒泡链路太长极端情况下性能会有点影响。但绝大多数业务场景这个影响可以忽略不计。4.3 自定义事件与 trigger 触发除了浏览器原生事件jQuery 还允许你自己定义事件名然后用.trigger()手动触发。这套机制非常适合模块解耦// 模块 A监听自定义事件 $(document).on(user.login, function(event, userInfo) { updateAvatar(userInfo.avatar); showWelcome(userInfo.name); }); // 模块 B用户登录成功后触发 $.ajax({ url: /api/login, method: POST, data: { /* ... */ } }).done(function(res) { $(document).trigger(user.login, [res.user]); });这样模块 A 和模块 B 不需要互相引用对方通过事件名就能协作。这在老项目的模块化改造中非常实用。trigger还有一个特性它会触发浏览器的默认行为。比如$(#fileInput).trigger(click);这样不用用户实际点击就能打开文件选择框。你如果是想“手动触发事件绑定的函数但不触发默认行为”那就用.triggerHandler()。这个方法还有个特点不会冒泡只会触发第一个匹配元素上的事件处理函数。4.4 事件命名空间解决“精准解绑”和“分组管理”事件命名空间是很多人没用过但确实好用的功能。写法就是在事件名后面加个点再接命名空间// 绑定带命名空间的事件 $(#btn).on(click.save, saveHandler); $(#btn).on(click.cancel, cancelHandler); // 只解绑 save 命名空间的事件不影响 cancel $(#btn).off(click.save);这个功能在组件销毁时特别好用。一个页面可能同时在多个模块里给同一个按钮绑了 click 事件如果盲目.off(click)会把别的模块的事件也干掉了。用命名空间可以精准拆解。4.5 阻止冒泡与阻止默认行为处理事件时有两行代码一定不陌生event.stopPropagation(); // 阻止冒泡 event.preventDefault(); // 阻止默认行为jQuery 里还有一种写法直接在事件处理函数的末尾return falsejQuery 会自动帮你在内部补上 stopPropagation 和 preventDefault。但这个写法太“隐式”了可读性不好而且如果你写的是原生 DOM 事件监听addEventListenerreturn false是没有任何作用的。所以我强烈建议显式调方法别靠 return false。这也是团队协作里比较 common 的约定。5. Ajax 封装与异步交互5.1 $.ajax 的核心参数与基本套路jQuery 的 Ajax 封装是当年它最吸引人的功能之一简化了跨浏览器 XMLHttpRequest 的繁琐操作。尽管现在大家更喜欢 fetch 或者 axios但你依然会在各种老代码里看到一堆$.ajax、$.get、$.post。标准写法$.ajax({ url: /api/user/list, method: GET, dataType: json, data: { page: 1, size: 20 }, success: function(res) { renderList(res.data); }, error: function(xhr, status, error) { console.error(请求失败, status, error); } });几个参数说明一下method请求方式老代码里也有写type的两者都行1.9 之后推荐method。dataType期望的返回类型填json时jQuery 会自动把返回的字符串解析成对象。如果解析失败会走 error。data发送到服务端的参数GET 请求会拼到 query string 上POST 请求会放到请求体中。jQuery 默认会把对象序列化成keyvaluekeyvalue的形式也就是表单格式application/x-www-form-urlencoded不是 JSON 格式。这是很多后端同事喜欢的方式但如果是新项目对接 JSON 接口你需要在请求头里额外设置$.ajax({ url: /api/user/create, method: POST, contentType: application/json; charsetutf-8, data: JSON.stringify({ name: 张三, age: 20 }), dataType: json });注意data传字符串和传对象是两回事。传对象时 jQuery 会帮你序列化传字符串时你得自己保证格式正确。我建议能传对象就传对象省心。5.2 $.get、$.post、$.getJSON 的简化写法如果只是简单的 GET 请求用$.get会简洁很多$.get(/api/user/info, { id: 1001 }, function(res) { console.log(res); }, json);参数分别是请求地址、query 参数、回调函数、期望的返回类型。$.post用法完全一样只是请求方式变了。$.getJSON是更专一的简写专门用于 GET 请求且返回 JSON$.getJSON(/api/data/dashboard, { from: 2024-01-01 }, function(data) { // 直接拿 data 使用 });在现在前端框架里大家已经习惯用 Promise 了jQuery 的 Ajax 其实也是支持链式.done()和.fail()的$.get(/api/user/list) .done(function(res) { renderList(res.data); }) .fail(function(xhr) { toast(加载失败); });这在老代码里面也不少。如果不想用回调风格也可以用async/await包一层jQuery 的 Ajax 方法默认返回的是一个 jqXHR 对象它是 thenable 的所以能直接awaitasync function loadUsers() { try { const res await $.get(/api/user/list); renderList(res.data); } catch (e) { toast(加载失败); } }注意$.get返回的 jqXHR 对象不是标准的 Promise但它实现了 then 接口在大多数场景下直接await没毛病。不过如果你需要.finally()某些旧版本 jQuery 支持得不好需要注意。5.3 全局事件ajaxStart、ajaxComplete 的应用如果你需要在所有 Ajax 请求开始时显示 loading请求结束后隐藏 loading一个个去写显然不现实。jQuery 提供了全局 Ajax 事件$(document).ajaxStart(function() { showLoading(); }); $(document).ajaxComplete(function() { hideLoading(); });注意一个细节ajaxStart是在“没有其他进行中的 Ajax 请求”时才会触发。如果同时有三个请求在飞第一个请求触发一次ajaxStart后面两个不会重复触发。ajaxComplete则是每个请求完成都会触发一次。所以最简单的 loading 逻辑会写成“开始时显示所有请求完成后隐藏”而不是“某个请求完成就隐藏”否则第一个请求结束 loading 就没了。更精细的控制可以在单个请求上绑局部事件$.ajax({ url: /api/data, beforeSend: function() { $(#loading).fadeIn(); }, complete: function() { $(#loading).fadeOut(); } });beforeSend在请求发出前调用complete不管成功失败都会调用。这两个是单请求级别的回调比全局事件更可控。5.4 跨域与 JSONP 的注意事项老项目中JSONP 是一个绕不开的跨域方案。jQuery 对它做了很好的封装核心就是dataType: jsonp$.ajax({ url: http://api.example.com/get-data, dataType: jsonp, jsonpCallback: callback, success: function(data) { console.log(data); } });jQuery 会动态地在页面里插入一个script标签并把callback参数拼到请求地址上服务端返回的 JS 代码其实就是callback({...})这个参数名可以通过jsonp指定。默认的 callback 参数名是callback但有的服务端接口自定义了参数名比如cb、jsonpcallback所以实际对接时一定要跟后端对好。JSONP 只支持 GET 请求没有 readyState、status 这些概念所以 error 回调基本形同虚设。如果接口超时或者服务端错误前端拿到的往往是 script 加载失败的报错。这个限制决定了 JSONP 只能作为一个临时方案新项目建议优先考虑 CORS。6. 动画与特效机制比效果更重要6.1 内置动画方法与标准参数jQuery 内置了一组常用动画覆盖了日常绝大部分“显示/隐藏”“淡入淡出”“滑入滑出”的需求.show()/.hide()直接改 display可以传速度参数但默认是无动画的。.fadeIn()/.fadeOut()/.fadeToggle()改变透明度最终切到 display:none。.slideDown()/.slideUp()/.slideToggle()改变高度常用于折叠面板、下拉菜单。.toggle()在显示和隐藏之间切换老版本可传速度。速度参数可以填fast、normal、slow也支持毫秒数比如500。注意这里大写的 speed 对应关系在不同版本里是一样的fast 200msnormal 400msslow 600ms。如果你需要更精确的时长直接写毫秒。6.2 animate 进阶自定义属性和回调时机.animate()是显示 jQuery 动画能力的核心方法你可以对任意 numeric 类型的 CSS 属性做动画$(#box).animate({ width: 300px, height: 200px, opacity: 0.5, left: 50px }, 300, function() { console.log(动画完成); });几个关键点属性值支持50px这种相对值写法表示在当前位置基础上增加 50px。opacity这种非像素属性也能动。颜色不能直接用.animate()会报错或者无效。想要颜色渐变要么用.css()过渡配合 CSS3 transition要么引入 jquery-color 插件。回调函数在动画完全结束后触发一次。如果你在回调里再触发动画注意形成死循环。6.3 stop 方法动画队列的救火队员这是 jQuery 动画里使用频率极高又总被忽略的方法。默认情况下同一元素上的多个动画会依次排队执行。如果你频繁触发动画比如鼠标快速滑过菜单动画队列会越积越多效果就是“菜单抖个不停怎么都停不下来”。解决办法就是.stop()。.stop()有两个常用的参数版本$(#menu).stop(); // 停止当前动画直接进入下一个队列动画 $(#menu).stop(true); // 清除所有动画队列但不跳转到最终状态 $(#menu).stop(true, true); // 清除队列并直接跳到当前动画的最终状态实际开发中我比较推荐 hover 类动画这样处理$(.menu-item).on(mouseenter, function() { $(this).find(.sub-menu).stop(true, true).fadeIn(200); }).on(mouseleave, function() { $(this).find(.sub-menu).stop(true, true).fadeOut(200); });这样快速滑过多个菜单时每个菜单的动画都会立即到达最终状态不会形成动画堆积交互感觉也干净利落。7. 遍历与数据存储不止是each7.1 each 遍历对象和数组一把梭$.each()是 jQuery 里使用频率极高的工具函数之一它可以遍历数组和对象且无需关心元素是不是 jQuery 对象。let arr [a, b, c]; $.each(arr, function(index, item) { console.log(index, item); }); let obj { name: 张三, age: 20 }; $.each(obj, function(key, value) { console.log(key, value); });注意回调函数里的参数顺序是(索引, 元素)不是(元素, 索引)。这个顺序跟原生的forEach也不一样原生forEach是(元素, 索引)。所以如果你同时写原生和 jQuery 代码非常容易搞反拿到 index 却以为在拿值。另一种常见用法是遍历 jQuery 对象集合$(li.item).each(function(index) { // 这里的 this 是当前遍历到的原生 DOM 元素不是 jQuery 对象 let $li $(this); if (index % 2 0) { $li.addClass(even); } });记住这个回调里的this是原生 DOM想用 jQuery 方法要$(this)包一下。如果你想用箭头函数this会变成外层作用域的 this而不是当前元素所以不要用箭头函数写 each 回调除非你明确知道自己在干嘛。7.2 $.map 与 $.extend数据加工与对象合并$.map类似于原生的数组 map遍历并返回一个新数组let doubled $.map([1, 2, 3], function(value, index) { return value * 2; }); console.log(doubled); // [2, 4, 6]它和$.each最大的区别是map 有返回值each 没有。需要修改数据时首选 map单纯遍历用 each。$.extend是一个在插件开发里极其重要的函数作用是合并对象let defaults { width: 100, height: 100, color: red }; let options $.extend({}, defaults, { width: 200 }); console.log(options); // { width: 200, height: 100, color: red }第一个参数传空对象是为了避免直接修改 defaults。如果不传空对象defaults.width会被改掉。这点在写插件时尤其重要。还有一种写法$.extend(true, {}, obj1, obj2)第一个参数传true表示深拷贝。深拷贝的意思是嵌套对象也会被递归合并而不是只复制引用。平时处理配置合并时我建议默认用true因为浅拷贝很容易在嵌套对象上出问题。7.3 $.data 与 jQuery 对象上的 data 方法$.data或者.data()是 jQuery 提供的数据缓存机制它不依赖 HTML 的>// 存数据 $(#card).data(user, { name: 张三, age: 20 }); // 取数据 let user $(#card).data(user);这个机制的好处是存的可以是 JS 对象、数组、函数不只是字符串。数据不会显示在 DOM 的 HTML 属性里避免被爬虫或审查工具看到。读取>// 错误示范永远成立 if ($(#not-exist)) { // jQuery 对象始终是 truthy 的 } // 正确做法判断 length if ($(#not-exist).length 0) { // 元素存在 }$(#not-exist)返回的也是一个 jQuery 对象它有length属性值为 0。但对象本身永远是真值所以直接if ($(...))判断不出来。在写代码时养成习惯所有需要判断元素是否存在的地方一律用.length。如果只是想“元素存在就执行某些操作不存在就什么也不做”也可以这样写$(.target).each(function() { // 没有匹配到的集合each 不会执行任何回调 });但这种写法可读性一般不如if ($(.target).length)直白。8.3 获取元素索引与筛选集合.index()不带参数时得到当前元素在同辈元素中的索引位置。.index(element)得到指定元素在当前集合中的索引位置。.filter(selector)在当前集合中过滤出符合条件的元素。.not(selector)在当前集合中排除符合条件的元素。.has(selector)保留包含匹配子元素的元素。.eq(index)取指定索引的元素支持负数eq(-1)是最后一个。.first()/.last()取第一个/最后一个。一个实用场景点击某个 tab 时要拿它的索引然后操作内容区对应 tab 面板$(.tab-item).on(click, function() { let index $(this).index(); $(.tab-panel).eq(index).addClass(active) .siblings().removeClass(active); });这里的.siblings()是另一个常用方法用于获取当前元素的所有同辈元素。配合.filter()或直接链式调用可以避免写很多重复代码。9. 性能优化与常见问题排查9.1 选择器的性能陷阱jQuery 的选择器越复杂查找元素的开销越大。虽然现代浏览器在querySelectorAll上已经非常快但我们在写代码时还是要注意几个习惯优先用 ID 选择器。如果最外层有一个固定 ID 的容器从它开始向下找比直接从全局找快。比如$(#app li.item)先定位到#app再在里面找 li性能明显好于$(li.item)。少用通配选择器和非常宽泛的选择器。$(*)和$(div span)这种代价很高。避免在循环里反复查询 DOM。比如循环里每次都用$(#list .item)这种代码应该放到循环外面存成变量。// 低效做法 for (let i 0; i 100; i) { $(.item).css(width, i); } // 高效做法 let $items $(.item); for (let i 0; i 100; i) { $items.eq(i).css(width, i); }每次调用$()都会触发一次 DOM 查找能缓存就缓存这个习惯在原生 JS 里同样适用。9.2 避免重复包装 jQuery 对象.click()回调里的$(this)如果在一个高频事件比如 mousemove、scroll里执行反复创建 jQuery 对象本身也会有性能开销。针对高频事件我通常会在回调开头先缓存$(#box).on(mousemove, function() { let $this $(this); // 后续都用 $this });另外ES6 的箭头函数绑定了外层的 this永远不会指向当前事件元素所以 jQuery 事件回调里我还是建议用function() {}而不是箭头函数。你只有在明确想拿到外层 this 时才用箭头函数。9.3 常见问题速查为什么我的选择器选不到元素很多刚接触 jQuery 的朋友都会遇到“明明页面上有元素但选择器就是选不到”的困惑。常见原因无外乎这么几种现象原因脚本在 DOM 加载前执行元素还没渲染自然选不到要把代码包在$(function() { ... })或$(document).ready(function() { ... })里选择器写错class 或 id 拼写错误、大小写不对、层级关系不对元素是动态加载的直接绑定的方法没法作用于新元素要改用事件委托属性值里有特殊字符nameuser.name时$([nameuser.name])可能解析出错要加引号是自己造的 jQuery 集合不是 DOM对$([])调用事件方法不会绑定任何东西排查时我一般打开浏览器的 console先$(目标选择器)看看返回的 length 是多少再对着Elements面板检查实际 DOM 结构。这个流程比反复改代码快得多。9.4 关于 jQuery 版本的小建议jQuery 3.x 是目前的主流版本它引入了大量对现代浏览器的优化移除了一些旧版的兼容代码体积也更小。如果你的项目没有历史包袱直接用 3.x。如果是老项目升不了级也不建议在关键业务里混用多个版本很容易因为全局$被覆盖导致各种莫名其妙的问题。再补一个实用的点jQuery 3.x 之后的$(document).ready()和$(function(){})依然支持但如果你用模块化工具比如 webpack 里 npm 安装的 jQuery要注意引入方式和全局$的定义。通常你需要显式import $ from jquery而不是天然就有全局的$可用。我个人在实际操作中的体会是jQuery 并不是“学了没用”的过时技术。在老项目维护、浏览器兼容修补、快速原型制作、甚至是一些轻量级页面里jQuery 依然比引入一个前端框架要轻快得多。真正做项目的过程中理解它那套“选择器 链式调用 隐式迭代”的模式会让你读别人代码时一目了然遇到需要和第三方插件协作时也更从容。上面这些核心知识点从选择器到事件再到大而全的 Ajax 封装和动画队列基本覆盖了日常开发 90% 的高频操作。把这些吃透你手上就有一把打开无数老项目维护之门的钥匙了。