jQuery

前言

  • 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
<!-- 1) 官方 / 国内镜像 CDN(npmmirror 取文件要走 /files/,少了这段会 404) -->
<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>

<!-- 2) 本地文件:生产环境更稳(不依赖第三方域名可达性) -->
<script src="/js/jquery-3.7.1.min.js"></script>

<!-- 3) 需要防篡改时加 SRI + crossorigin -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>

<!-- 4) 模块化写法:CommonJS 一直支持;ESM 是 4.0.0 才提供的 -->
<script type="module">
import $ from './node_modules/jquery/dist-module/jquery.module.js'; // 4.0.0 起
</script>
<script>
const $ = require('jquery'); // CommonJS:两个版本的 main 都指向 dist/jquery.js
</script>

四种构建的区别

构建 内容 用在哪
Uncompressed 带注释与完整变量名 开发调试、读源码
Minified 压缩混淆 生产环境默认选择
Slim 去掉 Ajax 与动画(4.0 的 slim 还多去掉 callbacks / deferred / queue) 确定不用 $.ajax / .animate 时;用 4.0 的 slim 连 $.Deferred 都没有
Slim Minified 精简 + 压缩 只要选择器和 DOM 操作的场景
1
$.fn.jquery;        // 实测:'3.7.1';4.0.0 的完整版读回 '4.0.0',slim 读回 '4.0.0+slim'

版本线:为什么本篇按 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 怎么做

  1. 先换成未压缩的开发版(dist/jquery.js),再在它后面挂上 jQuery Migrate 4.0。控制台会把你踩到的每一条移除项和行为变化打印出来,改完再把 Migrate 摘掉。
  2. Migrate 只管 3.x → 4.0 这一段,不会把 1.x / 2.x / 3.x 早期删掉的东西找回来,也不能同时挂多个版本。
  3. 上表第一行那些被删的工具函数都有原生替身,本来就不该依赖 jQuery。
  4. 在 Node 里喂 JSDOM 的写法变了:3.x 是 require('jquery')(window),4.0 要用工厂入口 require('jquery/factory')(window)(slim 用 jquery/factory-slim),或者先把 JSDOM 的 window 挂到 globalThis 再 import。
  5. $.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; // 1 —— 局部引用照常可用
window.$ === undefined; // true —— 全局 $ 已被释放
window.$ = jq; // 之后也可以再拿回来

上面三行是实测结果。更常见的写法是干脆不释放,只在闭包里固定别名:

1
2
3
4
5
6
(function ($) {
// 这个作用域里的 $ 一定指向 jQuery
$(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; // 3 实测:数字
$items[0]; // <p class="item odd"> 原生 DOM 元素
$items.get(1); // 同上,另一种写法
$items.eq(1); // 第二个元素,包成 jQuery 对象(可以继续链式)
$items.toArray(); // [p, p, p] 全部转原生数组
Array.isArray($items); // false 实测
$items instanceof jQuery; // true 实测
$.type($items); // 'object' 实测
  • 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();      // '二'     按下标取(从 0 开始)
$('.item').first().text(); // '一'
$('.item').last().text(); // '三(隐藏)' —— 实测:last 不看可见性,隐藏元素也算
$('.even').index(); // 1 —— 在同级兄弟中的位置
$('.even').index('#root > *'); // 1 —— 在给定集合中的位置
$('.item').slice(0, 2).length; // 2
  • :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 === el,都是原生 DOM 元素
$(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>'); // 覆盖式写入 HTML
$('#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);    // true = 连事件和数据一起复制
copy.hasClass('li-a'); // 实测 true
$('.li-b').wrap('<div class="w"></div>');
$('.li-b').parent().attr('class'); // 实测 'w'
$('.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');      // '/some/path?x=1'                     —— 原样
$('#link').prop('href'); // 'http://127.0.0.1:4099/some/path?x=1' —— 解析后的绝对地址

$('#chk').prop('checked', true);
// 实测:prop('checked') = true,attr('checked') 依然是 null —— 布尔状态只能用 prop

$('.item[hidden]').prop('hidden'); // true
$('.item[hidden]').attr('hidden'); // 'hidden'

$('#txt').val(); // '初始'
$('#txt').val('新值'); // 改当前值
$('#txt')[0].value; // '新值' —— 与原生同步

$('#sel').val(); // '2'(多选时返回数组)
$('input[name="rg"]:checked').val(); // 'r2'
  • 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'); // true
$('#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');                    // '50px'        实测
$('#box').css('backgroundColor'); // 'rgba(0, 0, 0, 0)' 实测:透明也照实返回
$('#box').css('color', 'rgb(1, 2, 3)'); // 单条设置
$('#box').css({ color: '#fff', padding: '4px' }); // 对象批量设置
$('#box').width(); // 50 内容宽
$('#box').innerWidth(); // 50 + padding
$('#box').outerWidth(); // 50 + padding + border(outerWidth(true) 再加 margin)
$('#box').offset(); // {left, top} 相对文档
$('#box').position(); // 相对最近的定位父元素
$('#box').scrollTop(); // 读/滚
$('#box').html('...'); // 尺寸相关操作后别忘了 resize/scroll 事件是原生 window 上的

实测 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
a b ns delegated

解绑后同样用实测验证:

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();            // display:none,并记住原值(inline-block 会被还原)
$('#box').show(); // 实测:display 回到 'block'
$('#box').toggle(); // 切换
$('#box').is(':visible'); // 实测 false(隐藏时)
$('#box').is(':hidden'); // 实测 true
$('#box').is(':animated'); // 实测 false(没在动画中)

$('#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
// $.extend 最常见的用法:插件参数默认值合并
$.fn.myPlugin = function (opts) {
const settings = $.extend({ color: 'red', size: 12 }, opts); // 不污染 defaults
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', // GET/POST/PUT/DELETE…(也兼容写 type)
data: { page: 1 }, // 对象会被 $.param 序列化
dataType: 'json', // 期望的响应类型;不写则按 Content-Type 猜
timeout: 5000,
traditional: false, // 数组序列化风格
beforeSend(xhr) { /* 加自定义头、显示 loading */ },
success(data, status, xhr) { /* 成功 */ },
error(xhr, status, err) { /* 失败:status 可能是 timeout / error / abort */ },
complete(xhr, status) { /* 无论成败,收尾(关 loading) */ }
});

// 简写
$.get('/api/list', { page: 1 }, (data) => { });
$.post('/api/add', { name: 'x' });
$.getJSON('/api/list', { page: 1 });
$('#box').load('/partial.html #list'); // 直接抓一段 HTML 塞进元素
  • $.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
// 给 jQuery 对象加方法:挂在 $.fn 上($.fn === jQuery.prototype)
$.fn.highlight = function (color) {
return this.each(function () { // 保持链式:返回 this
$(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(); // 这是 <a>,不拦会跳顶
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,体会三代写法。

参考资料

本文结束 感谢您的阅读
正在加载今日诗词....