前言
jQuery 是 2006 年出现的 DOM 操作库,它的价值在当时非常明显:统一了各家浏览器的差异 、用一套链式 API 把「选择 → 操作」写得极短。今天原生 DOM API(querySelector、classList、fetch、closest、Pointer Events)已经把这些能力补齐,新项目大多不再引 jQuery;但大量存量项目、后台模板、富文本/日历类插件仍然基于它 ,所以读懂它、会用它、知道它和原生的对应关系,仍然是必要技能。
本篇基于 jQuery 3.7.1 (本机下载后在 Chrome 里实测,$.fn.jquery 读回 3.7.1)。所有 API 表里的「实测」都跑过,包括全选/不选/反选、表头与条目复选框联动。
官方在 2026-01-17 发布了 jQuery 4.0.0 (npm 的 latest 已经是它),这是近十年第一个带破坏性变更的大版本。本篇正文仍按 3.7.1 讲,因为下面每一条输出都是在这一版上跑出来的;两版差在哪、要不要升级,见「引入、版本与 noConflict」里的「版本线」小节。
引入、版本与 noConflict
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <script src ="https://registry.npmmirror.com/jquery/3.7.1/files/dist/jquery.min.js" > </script > <script src ="https://registry.npmmirror.com/jquery/4.0.0/files/dist/jquery.min.js" > </script > <script src ="/js/jquery-3.7.1.min.js" > </script > <script src ="https://code.jquery.com/jquery-3.7.1.min.js" integrity ="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin ="anonymous" > </script > <script type ="module" > import $ from './node_modules/jquery/dist-module/jquery.module.js' ; </script > <script > const $ = require ('jquery' ); </script >
四种构建的区别
构建
内容
用在哪
Uncompressed
带注释与完整变量名
开发调试、读源码
Minified
压缩混淆
生产环境默认选择
Slim
去掉 Ajax 与动画(4.0 的 slim 还多去掉 callbacks / deferred / queue )
确定不用 $.ajax / .animate 时;用 4.0 的 slim 连 $.Deferred 都没有
Slim Minified
精简 + 压缩
只要选择器和 DOM 操作的场景
版本线:为什么本篇按 3.7.1 写,4.0.0 又改了什么
先把两条线说清楚:
3.7.1 :2023-08-28 发布,是 3.x 这一线的最后一次发布,也是存量项目、教程、插件生态里最常见的一版。
4.0.0 :2026-01-17 发布,npm 的 dist-tags.latest 已经是它,是近十年第一个带破坏性变更的大版本。
本篇正文按 3.7.1 讲,是因为每条「实测」输出都在它上面跑出来的——同一份代码在 4.0.0 上会直接得到不一样的结果,下面这张表就是这么对出来的(本机 Chrome,两个版本各取官方 dist 文件,跑同一个探测脚本)。
探测项
3.7.1 实测
4.0.0 实测
$.trim、$.type、$.isArray、$.isNumeric、$.isWindow、$.parseJSON、$.now、$.camelCase、$.nodeName、$.unique
都是 function
全部 undefined,官方给了原生替身:String.prototype.trim、typeof / Array.isArray / Object.prototype.toString.call、Number.isFinite、Date.now()、JSON.parse、$.uniqueSort
$.proxy、$.noop、$.parseHTML、$.Callbacks、$.Deferred、$.queue
function
仍是 function,没被删
$.fn.bind、.unbind、.delegate、.undelegate
function
仍是 function(官方早就标废弃,继续用 .on() / .off())
$.fn.push、.sort、.splice
function
undefined(从 jQuery 原型上摘掉了)
$.fx.interval
number
undefined(动画节奏交给 requestAnimationFrame)
$.fn.init 的形参个数
3
2(第三个没文档的 root 参数去掉了)
$('<div>').css('opacity')(元素还没进文档)
'1'
''(空串)
.toggleClass() 不带参数
类名被清空,实测 ''
什么都不做,实测保持 a b
.data() 返回对象上的 hasOwnProperty
正常可用
TypeError :数据对象改成 null 原型,判存在要写 'k' in $el.data() 或 Object.hasOwn(...)
$.event.special 的原型
继承 Object.prototype
null(同样别再调 hasOwnProperty)
slim 构建里还剩什么
ajax + effects 被去掉,$.Deferred、$.queue 还在
ajax + effects + callbacks / deferred / queue 都没了:$.Deferred、$.when、$.fn.delay、$.fn.promise 实测全 undefined
dist/jquery.min.js 体积
87,533 字节
78,748 字节
ESM 入口
npm 包里没有 ESM 构建
新增 dist-module/jquery.module.js,package.json 带 exports:jquery、jquery/slim、jquery/factory、jquery/factory-slim
浏览器支持也收紧了:4.0 只支持 IE11 和「当前版 + 上一版」的 Chrome / Edge / Firefox / Safari,Edge Legacy(非 Chromium)与 IE10 及以下不再支持。
3.x 升 4.0 怎么做
先换成未压缩的开发版 (dist/jquery.js),再在它后面挂上 jQuery Migrate 4.0 。控制台会把你踩到的每一条移除项和行为变化打印出来,改完再把 Migrate 摘掉。
Migrate 只管 3.x → 4.0 这一段,不会把 1.x / 2.x / 3.x 早期删掉的东西找回来,也不能同时挂多个版本。
上表第一行那些被删的工具函数都有原生替身,本来就不该依赖 jQuery。
在 Node 里喂 JSDOM 的写法变了:3.x 是 require('jquery')(window),4.0 要用工厂入口 require('jquery/factory')(window)(slim 用 jquery/factory-slim),或者先把 JSDOM 的 window 挂到 globalThis 再 import。
$.ajax 有两处行为变化(官方升级指南写的,我没做联网实测):dataType: 'json' 配 callback 不再自动升级成 JSONP,要 JSONP 必须显式写 dataType: 'jsonp';响应里的脚本不再自动执行,要用 $.getScript 或显式 dataType: 'script'。
该选哪一版
维护老项目:跟着项目现有版本走,别为了升级而升级;真要升就按上面 Migrate 的流程来。
新项目:先确认是不是真需要 jQuery(querySelector、fetch、classList、Pointer Events 基本够用)。确实要用、或者要用只认 jQuery 的插件时,按插件的兼容声明选 3.x 或 4.x。
跟着这篇笔记练手:用 3.7.1,正文输出才对得上;用 4.0.0 的话,上表里那些 undefined 会先绊你一下。
想知道自己项目用的是哪一版,控制台敲 $.fn.jquery 就行;想知道 npm 上现在有哪些版本,看 https://registry.npmmirror.com/jquery 返回 JSON 里的 dist-tags。
多个库抢 $:noConflict
jQuery 默认把 $ 和 jQuery 两个全局名都指向自己。原型链冲突(比如同时用 Prototype.js)时需要让出 $:
1 2 3 4 const jq = $.noConflict (); jq ('#root' ).length ; window .$ === undefined ; window .$ = jq;
上面三行是实测结果 。更常见的写法是干脆不释放,只在闭包里固定别名:
1 2 3 4 5 6 (function ($ ) { $(function ( ) { console .log ('ready' , $.fn.jquery ); }); })(jQuery);
和别的库共存也可以反过来:先 var $j = jQuery.noConflict(true) 把 $ 和 jQuery 都让出去,再自己命名。
核心概念:jQuery 对象是「类数组包装」
1 2 3 4 5 6 7 8 9 const $items = $('.item' );$items.length ; $items[0 ]; $items.get (1 ); $items.eq (1 ); $items.toArray (); Array .isArray ($items); $items instanceof jQuery; $.type ($items);
jQuery 对象 ↔ 原生 DOM 对象 互转
jQuery → 原生:$els[0]、$els.get(0)、$els.toArray()
原生 → jQuery:$(domEl)、$(document.getElementById('x'))、$(this)(在 each 回调里最常用)
为什么要转:原生对象才有 textContent、dataset、classList、appendChild、getBoundingClientRect 这些;jQuery 对象才有链式 API。
为什么 jQuery 对象不是数组:它有自己的原型($.fn),只有 length 和数字下标,map/filter/reduce 那些是 jQuery 版本($.map / .filter),语义与数组不同(比如 .filter 返回的还是 jQuery 对象)。
链式 :所有「设置」类方法都返回 jQuery 对象本身,所以可以一路串下去;「获取」类方法返回的是值(字符串/数字/布尔),链式到此中断。
1 2 $('#ul' ).children ().filter (':even' ).addClass ('on' ).end ().find ('li' ).first ().text ();
选择器
jQuery 选择器就是 CSS 选择器 + jQuery 自制的扩展 ,返回包装好的集合(找不到就是空集合,length === 0,不会 返回 null)。
类别
写法
实测结果
元素
$('p')、$('#ul li')
$('#ul li').length = 3
类 / id
$('.item')、$('#box')
$('.item').length = 3
属性存在
$('[data-v]')
3
属性相等
$('[data-v="2"]')
1
组合
$('p.item.odd')
2(含 hidden 那个)
层级
$('#root > p')、$('#root p')
3 / 3
分组
$('#box, #link')
2
伪类(CSS)
:first、:last、:even、:odd、:gt(1)、:lt(2)、:hidden、:visible
$('.item:not([hidden])').length = 2
表单
$(':checked')、$(':input')、$(':submit')
$('input[name="rg"]:checked').val() = 'r2'
内容过滤
:contains('文本')、:has(selector)
$('#root').has('ul').length = 1
转义特殊字符
$.escapeSelector('a:b')
实测得 a\\:b(会自动补 \\),ID 里有 : 或 . 时必用
1 2 3 4 5 6 $('.item' ).eq (1 ).text (); $('.item' ).first ().text (); $('.item' ).last ().text (); $('.even' ).index (); $('.even' ).index ('#root > *' ); $('.item' ).slice (0 , 2 ).length ;
:visible / :hidden 是 jQuery 自己实现的判断(display:none、visibility 无关、宽高为 0 都算 hidden),原生 CSS 里没有对应物。
选择器引擎就是浏览器的 querySelectorAll,所以能用标准 CSS 就用标准写法;:contains()、:has() 这些 jQuery 扩展在现代 CSS 里正在回归(:has() 已被浏览器原生支持)。
筛选与遍历
方法
作用
实测
.filter(sel 或 fn)
集合内保留匹配的
$('.item').filter(function (i) { return i % 2 === 0; }).length = 2
.not(sel)
集合内排除匹配的
$('.item').not('.odd').length = 1
.has(sel)
保留「后代里有匹配」的元素
$('#root').has('ul').length = 1
.is(sel)
判断是否匹配(返回布尔)
$('.even').is('.item') = true
.eq / first / last / slice
下标与切片
见上表
.each(fn)
遍历,this 是原生元素
实测按顺序取到 一 二 三(隐藏)
$.each(arrOrObj, fn)
通用遍历,回调参数 (indexOrKey, value)
实测 ['0:5', '1:6']
$.map(list, fn)
映射成新数组(返回原生数组 )
实测 [2, 4]
$.grep(list, fn)
过滤成原生数组
实测 [2, 4]
1 2 3 4 5 6 7 8 $('.item' ).each (function (i, el ) { $(this ).attr ('data-index' , i); }); $.each ({ a : 1 , b : 2 }, function (key, value ) { console .log (key, value); });
.each 里返回 false 不能 中断遍历(和 $.each 不同,后者返回 false 会 break)。要中断用 for (let i = 0; i < $els.length; i++)。
遍历 DOM 树
方向
方法
实测(对 #ul li 与 .even)
父
.parent()
$('#ul').parent().attr('id') = 'root'(只取直接 父元素)
祖先
.parents() / .parentsUntil()
$('li').parents('div').length = 1
最近匹配祖先
.closest()
$('li.li-a').closest('#root').length = 1(找不到返回空集合)
子
.children()
$('#ul').children().length = 3(只一层)
后代
.find()
$('#ul').find('li').length = 3
兄弟
.siblings()
$('.even').siblings().length = 7(同级全部,含非元素)
相邻
.next() / .nextAll() / .nextUntil()
$('.even').next().attr('class') = 'item odd'
向前
.prev() / .prevAll()
$('.li-b').prev().text() = 'A'
所有子节点
.contents()
$('#box').contents().length = 1(含文本节点)
回到上一步
.end()
链式中撤销最近一次筛选
.parent() 与 .parents() 的区别是「直接父」与「全部祖先」;做委托点击时用 .closest() 最贴切。
.children() vs .find():一层 vs 递归。
.contents() 会带文本节点,.children() 只有元素。
文档处理:增删改、克隆、包裹
插入
1 2 3 4 5 6 $('#host' ).append ('<b>a</b>' , '<i>b</i>' ); $('#host' ).prepend ('<em>z</em>' ); $('#host' ).after ('<div id="after1">A</div>' ); $('#host' ).before ('<div id="before1">B</div>' ); $('#host' ).html ('<span>x</span>' ); $('#host' ).text ('<span>x</span>' );
实测插入顺序:三个调用之后 #host 的 innerHTML 是 <em>z</em><b>a</b><i>b</i> —— prepend 排在最前,多个 append 参数按顺序 。相邻插入也验证过:$('#before1').next().attr('id') 得到 host,$('#host').next().attr('id') 得到 after1。
还可以把已存在的元素 移动过去(不是复制):$(sel).appendTo(target) / prependTo / insertAfter / insertBefore,是上面几个的反向写法。
删除、清空、替换
方法
效果
实测
.remove(sel?)
从 DOM 摘掉,并清掉数据与事件
—
.detach(sel?)
从 DOM 摘掉,保留数据与事件 ,便于再插回去
摘走 <li> 后 #ul 子项 3 → 2,appendTo('#ul') 回到 3
.empty()
只清空子节点,保留自身
调用后 children().length = 0
.replaceWith(x) / $(x).replaceAll(sel)
用新内容替换
#replaced 存在 1 个
克隆与包裹
1 2 3 4 5 6 7 const copy = $('.li-a' ).clone (true ); copy.hasClass ('li-a' ); $('.li-b' ).wrap ('<div class="w"></div>' ); $('.li-b' ).parent ().attr ('class' ); $('.li-b' ).unwrap (); $('p' ).wrapAll ('<div class="group"></div>' ); $('li' ).wrapInner ('<b></b>' );
克隆体会把 id 一起复制(实测克隆的 li 仍带 p1 这类 id),插回同一页面要立刻改掉,否则 getElementById 行为不可预期、HTML 校验也不通过。
clone() 不复制内联事件之外的东西?会复制 on* 属性;但只有 clone(true) 才复制 addEventListener 系(jQuery on 绑的)监听器与 .data()。
属性、值、样式与位置
attr() vs prop():这是 jQuery 最容易写错的一组
目标
用哪个
实测证据
HTML 里声明的特征:class、id、href、src、title、data-*
.attr()
$('#box').attr('title') = '提示';attr('data-x', '7') 后 attr('data-x') = '7'
DOM 对象的当前状态:checked、selected、disabled、value、hidden
.prop()
见下三行
表单控件的当前内容
.val()
.val('新值') 后 val() 与 prop('value') 都是 '新值'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $('#link' ).attr ('href' ); $('#link' ).prop ('href' ); $('#chk' ).prop ('checked' , true ); $('.item[hidden]' ).prop ('hidden' ); $('.item[hidden]' ).attr ('hidden' ); $('#txt' ).val (); $('#txt' ).val ('新值' ); $('#txt' )[0 ].value ; $('#sel' ).val (); $('input[name="rg"]:checked' ).val ();
attr('title', undefined) 不会 删属性(实测仍能读到 '提示');要删用 .removeAttr('title')。
非标准名字写在 prop 上时,不会变成 HTML 属性(实测 prop('title2','js-only') 后 attr('title2') 是 null)。
自定义数据一律走 data-* / attr 或 .data();.data() 会做类型猜测(data-n="7" 取到数字 7)并缓存,改 HTML 属性不会同步进 .data(),这点最容易被咬。
类名
1 2 3 4 $('#box' ).addClass ('a b' ); $('#box' ).removeClass ('b' ); $('#box' ).hasClass ('a' ); $('#box' ).toggleClass ('c' );
实测类名轨迹:a b → a → a c(依次执行 addClass/removeClass/toggleClass)。
toggleClass(name) 返回的是 jQuery 对象 (可继续链式),不是布尔;只有 toggleClass(name, boolean) 的两参形式才按布尔强制。
样式与尺寸
1 2 3 4 5 6 7 8 9 10 11 $('#box' ).css ('width' ); $('#box' ).css ('backgroundColor' ); $('#box' ).css ('color' , 'rgb(1, 2, 3)' ); $('#box' ).css ({ color : '#fff' , padding : '4px' }); $('#box' ).width (); $('#box' ).innerWidth (); $('#box' ).outerWidth (); $('#box' ).offset (); $('#box' ).position (); $('#box' ).scrollTop (); $('#box' ).html ('...' );
实测 width/innerWidth/outerWidth 都是 50(该元素没写 padding/border)。
.css() 读多值时传数组:$(el).css(['width', 'height']) 返回普通对象。
尺寸读数是 number (不含单位),.css('width') 是字符串 (含单位)。
批量样式改动请用类名切换,别一条条 .css():既难维护又会多次触发重排。
事件
绑定与解绑
1 2 3 4 5 6 7 8 $('#eb' ).on ('click' , function ( ) { console .log ('a' ); }); $('#eb' ).on ('click' , function ( ) { console .log ('b' ); }); $('#eb' ).on ('click.my' , function ( ) { console .log ('ns' ); }); $('#root' ).on ('click' , '#eb' , function ( ) { console .log ('delegated' ); }); $('#eb' ).trigger ('click' );
实测四次输出的顺序:
解绑后同样用实测验证:
1 2 3 off('click.my') 之后 -> a b delegated (只有命名空间那个被摘掉) off() 之后 -> delegated (自身全摘,委托仍在) one('click', fn) 两次触发 -> once (只执行一次)
写法
作用
.on(type, handler)
绑定;type 可写 'click keyup' 或 {click: fn, mouseenter: fn}
.on(type, selector, data, handler)
事件委托 (data 可选,放在 handler 前)
.one(...)
只触发一次
.off(type.ns, handler)
解绑;off() 全解,off('.ns') 按命名空间解
.trigger(type, extraArgs)
手动触发(会走默认行为,如 click 到 a 会跳转);.triggerHandler() 只跑处理器不跑默认行为
事件对象
e.target 真实来源、e.currentTarget / this 绑定者、e.type、e.namespace、e.data、e.result、e.timeStamp;e.isDefaultPrevented() 判断是否已被拦
实测事件对象:isDefaultPrevented=true、target=A、currentTarget=link、type=click,且 e instanceof $.Event 为 true(jQuery 把原生事件包成了自己的 Event 对象)。
自定义事件也能触发并带参:$el.on('myevt', (e, x, y) => …) + $el.trigger('myevt', [1, 2]) → 实测拿到 1/2。
老 API 的现在状态(实测 jQuery 3.7.1)
下表是 3.7.1 的状态。换到 4.0.0,$.trim / $.type / $.isArray 这批工具函数已经直接是 undefined,$.fn.push / .sort / .splice 也没了,详见前面「版本线」小节的对照表。
API
实测
结论
.live()
undefined
jQuery 1.9 废弃、3.0 移除;用 .on('click', sel, fn) 委托替代
.delegate() / .undelegate()
仍是 function
官方标记废弃,行为等同委托写法,别再用
.bind() / .unbind()
仍是 function
官方标记废弃,别再用
.hover() / .toggle(fn…)
.hover 可用
hover 还常用;元素级的 .toggle(fn1, fn2) 已移除(.toggle() 现在是显隐)
$(document).ready / $(fn)
可用
3.x 起也可直接 $(function(){});现代浏览器里 ready 本质就是 DOMContentLoaded
键盘/鼠标对象的差异
e.pageX/pageY 是 jQuery 统一算好的(原生在老浏览器上不统一)。
e.which 对键盘=字符码、对鼠标=按键号(1/2/3),原生 which 已废弃 (实测 trigger 出来的合成事件里 which 是 undefined),现在更推荐直接用 e.key / e.button。
jQuery 事件对象上还挂着 originalEvent,需要原生细节时从它取。
效果(显隐、淡入淡出、滑动、自定义动画)
1 2 3 4 5 6 7 8 9 10 11 12 13 $('#box' ).hide (); $('#box' ).show (); $('#box' ).toggle (); $('#box' ).is (':visible' ); $('#box' ).is (':hidden' ); $('#box' ).is (':animated' ); $('#box' ).fadeIn (20 ); $('#box' ).fadeOut (20 ); $('#box' ).fadeToggle (20 ); $('#box' ).slideDown (20 ); $('#box' ).slideUp (20 ); $('#box' ).slideToggle (20 ); $('#box' ).animate ({ left : 10 , opacity : .5 }, 300 , 'swing' , cb); $('#box' ).delay (200 ).fadeOut (200 ); $('#box' ).stop (true , true ); $('#box' ).finish ();
实测:
hide() 后 css('display') === 'none'、:hidden 为 true、:animated 为 false。
show() 回到 block。
fadeOut(20) 的回调里 display 确认为 none。
说明:slide* / animate / 队列这几条我没能在自动化标签页里跑完整条动画链 ——页面处于后台时浏览器的定时器节流会把动画链拖住(这正是「后台标签页动画不准」的真实原因)。上面这几行的语义按官方文档给出,显隐与 fadeOut 回调部分为实测。
stop(clearQueue, jumpToEnd) 两个参数的含义要背:(false,false) 只停当前(默认)、(true,true) 常用于「输入框实时建议」这种要立刻打断的场景。
jQuery 动画自带效果队列 :同元素上的动画按顺序排,.delay() 往队列里插时间,.queue()/.dequeue() 可以手动控制。
现代做法:能用 CSS transition / animation 就别用 .animate(),让动画跑在合成线程上。
工具函数
函数
实测结果
说明
$.each([5,6], fn)
0:5 1:6
遍历数组/对象
$.map([1,2], v => v*2)
[2, 4]
返回原生数组 (Array.prototype.map 现在更好用)
$.grep([1,2,3,4], v => v%2===0)
[2, 4]
过滤成原生数组
$.extend(true, {}, a, b)
{"x":{"y":1,"z":2}},且原对象 a.x.z 仍是 undefined
第一个参数 true = 深合并 ;不传就是浅合并(会改第一个对象)
$.type([])
'array'
返回小写类型名(还有 $.isPlainObject、$.isEmptyObject、$.isFunction 全实测为 true)
$.isArray([])
true
3.x 已标废弃,改用 Array.isArray
$.trim(' x ')
'x'
3.5 起标废弃,改用 String.prototype.trim
$.param({a:1,b:[2,3]})
a=1&b%5B%5D=2&b%5B%5D=3
对象转查询串(数组变成 b[])
$.escapeSelector('a:b')
a\\:b
转义 id/class 里的特殊字符
$.proxy(fn, ctx)
回调里 this 指向 ctx,实测拿到 'obj'
等价于原生 fn.bind(ctx)
$.Callbacks()
存在
多播回调列表(add/ignore/fire),jQuery 内部用它实现 Deferred
$.Deferred()
resolve('v') 后 done 回调拿到 'v',typeof d.promise === 'function'
类 Promise;现代代码直接用原生 Promise
$.when(p1, p2)
存在
等价 Promise.all
$.support
object
特性探测集合(很多项已废弃,改用现代特性检测)
1 2 3 4 5 6 7 $.fn.myPlugin = function (opts ) { const settings = $.extend ({ color : 'red' , size : 12 }, opts); return this .each (function ( ) { $(this ).css ({ color : settings.color , fontSize : settings.size }); }); };
Ajax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $.ajax ({ url : '/api/list' , method : 'POST' , data : { page : 1 }, dataType : 'json' , timeout : 5000 , traditional : false , beforeSend (xhr ) { }, success (data, status, xhr ) { }, error (xhr, status, err ) { }, complete (xhr, status ) { } }); $.get ('/api/list' , { page : 1 }, (data ) => { }); $.post ('/api/add' , { name : 'x' }); $.getJSON ('/api/list' , { page : 1 }); $('#box' ).load ('/partial.html #list' );
$.ajax 返回 jqXHR (带 done/fail/always,也符合 Promise 习惯,可以 $.when(...)),但它是 jQuery 自己的 Deferred,不是原生 Promise;要 await 可以 await $.ajax(...)(jqXHR 有 then,能被 await)。
跨域 :浏览器同源策略。两种解法——服务端返回 CORS 头(首选),或 dataType: 'jsonp'(只能 GET、要服务端配合、本质是插 <script>,安全性差,现已基本淘汰)。
表单提交用 new FormData(form) 交给 fetch/$.ajax({ data: fd, processData: false, contentType: false })。
新项目直接用 fetch:
1 2 3 4 5 6 7 async function getList (page ) { const res = await fetch (`/api/list?page=${page} ` ); if (!res.ok ) { throw new Error ('HTTP ' + res.status ); } return res.json (); }
本篇的 Ajax 部分没有连真实后端跑通链路(本机只有一个表单回显服务用于《HTML入门》的实测),所以这里只给标准用法,不做「实测」标注。
插件机制
1 2 3 4 5 6 7 8 9 10 11 12 $.fn.highlight = function (color ) { return this .each (function ( ) { $(this ).css ('background-color' , color || 'yellow' ); }); }; $('.item' ).highlight ('#eee' ).text (); $.reverse = function (arr ) { return arr.slice ().reverse (); };
写插件的三条约定:return this 保链式 、在 each 里操作单个元素 、用命名空间存状态 ($(el).data('plugin_name', instance)),并考虑重复调用与卸载(off + removeData)。
实战案例
以下例子都按「HTML 结构 + 关键脚本」保留,脚本逻辑在本机 jQuery 3.7.1 下跑过(尤其第一个)。
全选 / 全不选 / 反选(实测通过)
1 2 3 4 5 6 7 8 <button id ="select_all" > 全选</button > <button id ="unselect_all" > 全不选</button > <button id ="toggle_selection" > 反选</button > <div class ="checkbox-group" > <label > <input type ="checkbox" class ="like" > 体育</label > <label > <input type ="checkbox" class ="like" > 音乐</label > <label > <input type ="checkbox" class ="like" > 美食</label > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 $(function ( ) { $('#select_all' ).on ('click' , function ( ) { $('.like' ).prop ('checked' , true ); }); $('#unselect_all' ).on ('click' , function ( ) { $('.like' ).prop ('checked' , false ); }); $('#toggle_selection' ).on ('click' , function ( ) { $('.like' ).prop ('checked' , function (i, val ) { return !val; }); }); });
实测勾选数量轨迹:初始 0 → 全选 3 → 全不选 0 → 反选 3 → 再反选 0 。原笔记用 each 手写的版本同样验证可用(实测得到 3):
1 2 3 $('.like' ).each (function ( ) { $(this ).prop ('checked' , !$(this ).prop ('checked' )); });
表头复选框与条目联动(含「全不选时表头取消」,实测)
1 2 3 4 5 6 7 8 $(function ( ) { $('#all' ).on ('change' , function ( ) { $('.like' ).prop ('checked' , this .checked ); }); $('.checkbox-group' ).on ('change' , '.like' , function ( ) { $('#all' ).prop ('checked' , $('.like' ).length === $('.like' ).filter (':checked' ).length ); }); });
实测:点表头后 3 个条目全选中;把其中一个取消并触发 change 后,表头 checked 变回 false。
目录导航展开 / 收起
1 2 3 4 5 6 7 8 <ul class ="menu" > <li > <a href ="#" class ="toggle" > 第一章</a > <ul class ="sub hide" > <li > 1.1</li > <li > 1.2</li > </ul > </li > <li > <a href ="#" class ="toggle" > 第二章</a > <ul class ="sub hide" > <li > 2.1</li > </ul > </li > </ul >
1 2 3 4 5 6 7 8 9 $(function ( ) { $('.menu' ).on ('click' , '.toggle' , function (e ) { e.preventDefault (); const $sub = $(this ).next ('.sub' ); $sub.slideToggle (200 ); $(this ).toggleClass ('open' ); $('.menu .sub' ).not ($sub).slideUp (200 ); }); });
要点:一个委托监听器覆盖所有层级;$(this).next('.sub') 精确取到紧邻的子列表。
列表项左右移动(双列表穿梭)
1 2 3 4 <select id ="left" multiple size ="6" > <option > 苹果</option > <option > 香蕉</option > </select > <button id ="to_right" > →</button > <button id ="to_left" > ←</button > <select id ="right" multiple size ="6" > </select >
1 2 3 4 5 6 7 8 9 10 11 $(function ( ) { $('#to_right' ).on ('click' , function ( ) { $('#left option:selected' ).appendTo ('#right' ); }); $('#to_left' ).on ('click' , function ( ) { $('#right option:selected' ).appendTo ('#left' ); }); $('#left, #right' ).on ('dblclick' , function ( ) { $(this ).find ('option:selected' ).appendTo (this .id === 'left' ? '#right' : '#left' ); }); });
appendTo 这类「把已有节点搬到另一个容器」的用法同样适用于 li、tr、div(实测 detach + appendTo 的组合可用)。
选项卡 + 自动轮播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $(function ( ) { const $tabs = $('.tab' ); const $panes = $('.pane' ); let index = 0 ; let timer = null ; function activate (i ) { index = i; $tabs.removeClass ('active' ).eq (i).addClass ('active' ); $panes.hide ().eq (i).fadeIn (200 ); } $tabs.on ('click' , function ( ) { activate ($(this ).index ('.tab' )); }); function play ( ) { timer = setInterval (function ( ) { activate ((index + 1 ) % $tabs.length ); }, 3000 ); } $('.banner' ).on ('mouseenter' , () => clearInterval (timer)); $('.banner' ).on ('mouseleave' , play); activate (0 ); play (); });
$(this).index('.tab') 取当前标签在同类中的位置(实测 index() 可取到同级位置)。
轮播必须在离开时 clearInterval,否则用户切到别的标签页也在偷偷跑(而且后台会被节流,回来时容易连环触发)。
商品展示(数量增减 + 小计联动)
1 2 3 4 5 6 7 8 9 10 11 12 $(function ( ) { $('.cart' ).on ('click' , '.btn' , function ( ) { const $input = $(this ).siblings ('input.qty' ); let n = parseInt ($input.val (), 10 ) || 1 ; n += ($(this ).hasClass ('plus' ) ? 1 : -1 ); $input.val (Math .max (1 , n)); const row = $(this ).closest ('tr' ); row.find ('.subtotal' ).text ((parseFloat (row.data ('price' )) * n).toFixed (2 )); const total = $('.subtotal' ).toArray ().reduce ((s, el ) => s + parseFloat (el.textContent ), 0 ); $('#total' ).text (total.toFixed (2 )); }); });
要点:closest('tr') 定位行、siblings 找同行控件、data-* 存单价、toArray().reduce 汇总。注意 parseInt 要写第二个参数。
常见坑速查
$(sel) 返回空集合不报错 → 后续链式静默什么都不做。排查时先看 .length。
脚本先于 DOM 执行 → 用 $(function(){}) 或 defer;jQuery 3.x 里如果 DOMContentLoaded 已经过,ready 回调依然是异步微任务执行(不是立即)。
用 attr('checked') 判断是否勾选 → 永远拿不到最新状态(实测 attr 是 null),必须 .prop()。
用 attr('value') 改输入框 → 只改了初始值,界面不变,必须 .val()。
remove() 之后再插回去,事件没了 → 用 detach()。
clone() 插回同一页面出现重复 id / 事件丢失 → 用 clone(true) 并改 id。
off() 摘不掉 → 传的不是同一个函数引用;改用命名空间 off('.ns') 或 AbortController 风格集中管理。
动态插入的元素点了没反应 → 直接绑定时机不对,改写成委托 parent.on('click', childSel, fn)。
链式断了 → 中间某步用了 .text() / .val() / .attr(name) 这类「读」方法,它返回的是值,不是 jQuery 对象。
$.each 和 .each 的参数记混 → .each(function (i, el) {...})(this 是原生元素);$.each(obj, function (k, v) {...})。
动画在后台标签页不跑 (实测遇到过:动画链停在中间一步)→ 浏览器会节流定时器;别把业务逻辑写在动画回调里。
多个库抢 $ → $.noConflict() 或立即执行函数固定别名。
老教程里的 .live() / .delegate() / $.browser → 都已移除($.fn.live 实测 undefined),改成委托与现代特性检测。
jQuery 1.x/2.x 与新代码混用 → 很多行为不同($.ajax 的 Promise 形态、prop/attr 语义、:visible 判定),升级时重点回归这些地方。
练习
用 jQuery 重写《DOM 和 BOM》里那个待办清单(新增、勾选、删除、localStorage 持久化),并在旁边写出对应的原生 DOM 版本,对比行数与可读性。
给一个静态表格加:点击表头按该列排序、搜索框实时过滤行、行悬停高亮。全部用委托实现。
写一个 $.fn.highlight 插件,支持 $('p').highlight() 与 $('p').highlight('#eee') 两种调用,并要求可链式。
实现「图片懒加载」:先放 data-src,滚动到可视区再 attr('src', …);然后改用原生 IntersectionObserver 重写一遍。
把 $.ajax 的 success/error/complete 改写为 done/fail/always,再改写为 async/await + fetch,体会三代写法。
参考资料