从 HTML 和 CSS 走进 JavaScript 和 jQuery,其实是大多数前端初学者最期待的一步。前两篇我们搞定了页面结构和视觉样式,但页面还是“静态”的,点击按钮没反应,数据没法动态展示,交互全靠浏览器默认行为。到了这一篇,事情开始变得有意思了:JS 负责让页面“动起来”,jQuery 则把最常用、最繁琐的 DOM 操作和事件处理封装成简洁的语法,让你用更少的代码完成同样的事情。这篇教程默认你已经掌握了 HTML 标签和 CSS 基础样式,跟着我把 JS 核心语法过一遍,再用两个完整的实战案例把 jQuery 落地,整个过程不需要框架、不需要构建工具,一个浏览器加一个编辑器就能跑通。
这篇内容适合两类人:一类是刚学完 HTML/CSS、对“动态页面”完全没概念的新手;另一类是写过一些 JS 但始终没搞明白 jQuery 到底解决什么问题、什么时候该用它的人。我会先把 JS 最核心的语法用最短篇幅讲清楚,然后直接进入实战——你会发现,很多网上的教程只讲语法不讲场景,结果看完还是不会写页面。我这篇反着来,一切从实际页面需求出发,语法只讲用得上的,原理只讲影响代码的那部分。
1. 先把 JavaScript 地基打牢:变量、函数、事件与数组
1.1 变量声明:var / let / const 怎么选才对
很多新手刚开始写 JS 时,根本不在乎变量声明关键词的区别,看到教程里写 var 就跟着写 var。等你写过几百行代码后就会明白,这里面的坑远比想象中多。var 的问题是函数级作用域和变量提升,这会导致一些“看起来不该出错却出错”的情况。
我给你的建议非常直接:默认用 const,确定要重新赋值时用 let,不要主动用 var。为什么?const 声明的是常量引用,能防止你手滑把重要的变量覆盖掉;let 是块级作用域,只在当前花括号内有效,不会泄露到外面去。
javascript复制// 错误示例:用 var 在循环里定义变量,循环结束后变量依然存在
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // 输出 3、3、3
}, 100);
}
// 正确示例:用 let 块级作用域,每次循环保留独立的值
for (let j = 0; j < 3; j++) {
setTimeout(function () {
console.log(j); // 输出 0、1、2
}, 100);
}
上面这个例子非常经典。用 var 时,循环结束后 i 的值已经变成了 3,三个定时器回调共享同一个 i,所以全是 3;改用 let 后,每次循环的 j 都是独立的一份,结果就是 0、1、2。理解了这一点,你就避开了新手阶段最容易踩的一个大坑。
1.2 函数声明与函数表达式:为什么有时“点击没反应”
JavaScript 里定义函数有两种常用方式,新手经常混淆。第一种是函数声明,会被提升到当前作用域顶部,所以你在声明之前调用也是可以的。第二种是函数表达式,它本质是把一个匿名函数赋值给变量,不会被提升,必须先赋值再调用。
javascript复制// 函数声明:可以提前调用
sayHello(); // 正常输出
function sayHello() {
console.log('你好');
}
// 函数表达式:必须先赋值
sayBye(); // 报错:sayBye is not defined
var sayBye = function () {
console.log('再见');
};
在实战中,jQuery 的事件绑定常常用到函数表达式,比如 $('#btn').click(function () { ... }) 这里面的匿名函数。如果你把函数声明和函数表达式搞混,最容易出现的情况是:页面加载时报错,事件绑不上,按钮点了没反应。这个错误在浏览器控制台里很直观,一看到 is not defined 或者 is not a function,马上想到这个原因。
1.3 事件:页面交互的核心机制
JS 让页面“动起来”的本质,就是监听事件并响应。浏览器里有大量事件类型:鼠标点击 click、鼠标移入 mouseenter、输入框内容变化 input、键盘按下 keydown、页面加载完成 DOMContentLoaded 等等。事件绑定最基础的写法有两种:一种是在 HTML 标签上写 onclick 属性,另一种是在 JS 里用 addEventListener。
我强烈建议你使用 addEventListener,比在 HTML 里写 onclick 干净得多。HTML 结构负责语义,JS 负责行为,两者混在一起后期维护非常痛苦。jQuery 的 click 方法本质上就是对 addEventListener 的进一步封装。
javascript复制// 传统方式:HTML 标签上写 onclick
// <button onclick="handleClick()">按钮</button>
// 推荐方式:在 JS 中绑定
document.getElementById('btn').addEventListener('click', function () {
console.log('按钮被点击了');
});
1.4 数组操作的实用场景:添加、判断、循环
数组是 JS 里最常用的数据结构,热搜词里有一条“jquery var arr=[] 添加 判断arr有没有数据”,这正好是数组操作最典型的两个需求。我直接给你一个可以直接抄的写法:
javascript复制var arr = [];
// 添加数据
arr.push('苹果');
arr.push('香蕉');
arr.push('橙子');
// 判断数组有没有数据
if (arr.length > 0) {
console.log('数组不为空,当前有 ' + arr.length + ' 条数据');
} else {
console.log('数组为空');
}
// 遍历数组
arr.forEach(function (item, index) {
console.log('第 ' + index + ' 项:' + item);
});
push 方法是在数组末尾添加一个元素,length 属性是数组的元素个数,forEach 是遍历数组的常用方法。这三个是你在实际项目里最高频用到的数组操作。还有一个常见需求是判断某个值是否在数组里,可以使用 indexOf 方法:返回值大于等于 0 表示存在,等于 -1 表示不存在。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 理解 jQuery 的设计哲学:写得更少,做得更多
2.1 jQuery 到底是什么,为什么现在依然值得学
jQuery 是一个 JavaScript 函数库,它解决了三个核心问题:浏览器兼容性、DOM 操作的繁琐写法、以及 AJAX 请求的复杂性。虽然现在原生 JS 和现代框架已经很成熟,但在很多老项目、WordPress 主题、后台管理系统中,jQuery 依然无处不在。学会它,你不仅多了一项维护老代码的技能,更重要的是,你能通过它理解“封装”和“链式调用”这两个重要的编程思想。
jQuery 的核心设计哲学是“Write Less, Do More”。它的选择器语法直接复用了 CSS 选择器,你会写 CSS 就会用 jQuery 选择元素。比如 $('#id') 选择 id 为 id 的元素,$('.class') 选择 class 为 class 的所有元素,$('div') 选择所有 div 元素。
javascript复制// 原生 JS 修改文本内容
document.getElementById('title').innerHTML = '新标题';
// jQuery 版本
$('#title').html('新标题');
看起来只是变短了一点点,但当你需要链式操作时,优势就体现出来了:
javascript复制// 一次操作做三件事
$('#title')
.html('新标题')
.css('color', 'red')
.addClass('highlight');
这就是链式调用,每个方法执行完后返回 jQuery 对象本身,所以你可以继续调用下一个方法。代码变得非常简洁,逻辑也一目了然。
2.2 jQuery 引入方式与版本选择的经验
引入 jQuery 的方式有两种:本地下载引入和 CDN 引入。我建议学习阶段直接用 CDN,简单快捷,不需要额外管理文件。
html复制<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
版本选择上,我推荐使用 3.x 系列。jQuery 3.x 已经完全放弃了对 IE6-8 的支持,但换来了更简洁的 API 和更好的性能。如果你维护的是非常老的项目,才需要考虑 1.x 或 2.x。
有一个实操细节要注意:<script> 标签的加载是会阻塞页面解析的。所以 jQuery 的 script 标签要放在 HTML 的底部、</body> 之前,或者使用 defer 属性。否则你在头部引入了 jQuery,下面的 HTML 还没解析完,jQuery 就找不到任何 DOM 元素。
2.3 从 DOMContentLoaded 到 jQuery 的 ready 写法
页面加载时存在一个关键时机问题:你写好的 JS 代码在什么时候运行?如果脚本在 HTML 顶部加载,而脚本里要操作页面下方的某个元素,此时元素还没被解析出来,代码就会报错。原生 JS 的解决方案是监听 DOMContentLoaded 事件,jQuery 则把它封装成了 $(document).ready()。
javascript复制// 原生写法
document.addEventListener('DOMContentLoaded', function () {
// 页面结构加载完毕后执行
});
// jQuery 写法
$(document).ready(function () {
// 页面结构加载完毕后执行
});
// 更简洁的 jQuery 写法
$(function () {
// 页面结构加载完毕后执行
});
这三种写法表达的意思是一样的,实际开发中我习惯用第三种,最简短。值得注意的是,$(fn) 并不等同于 window.onload,后者需要等待页面上的所有图片、样式等资源加载完成,前者只需要 DOM 结构就绪。如果你的代码不涉及图片宽高计算,用 ready 就够了,响应速度更快。
2.4 jQuery 的 XSS 风险:html() 方法要小心
搜索热词里有“jquery xss”,这个必须展开说。jQuery 的 .html() 方法可以往页面注入 HTML 片段,这非常方便,但也带来了 XSS(跨站脚本攻击)风险。如果你把用户输入的内容直接放进 .html() 里,用户就可能在里面注入一段 <script> 标签或者事件属性,从而执行恶意脚本。
javascript复制// 危险做法:把用户输入直接当 HTML 插入
var userInput = '<img src=x onerror="alert(\'黑客代码\')">';
$('#content').html(userInput); // 弹窗被执行
// 安全做法:用 text() 方法,内容只会被当作文本
$('#content').text(userInput); // 显示为纯文本
判断标准非常简单:如果内容只是纯文本,使用 .text();只有在你确实需要渲染 HTML 标签、并且内容来自可信来源时,才使用 .html()。如果必须展示用户输入的 HTML,请务必做过滤和转义。这个问题在很多老项目里真实存在过,不是纸上谈兵。
3. 实战一:用 jQuery 实现一个分类筛选列表
3.1 需求场景梳理
这个案例的需求非常典型:页面上有一组商品卡片,顶部有“全部”、“水果”、“蔬菜”三个筛选按钮,点击按钮后,商品列表根据分类进行筛选,并实时更新显示数量。这个功能在电商、企业官网、作品集网站上非常常见。
我把 HTML 结构先写出来,方便你对照理解:
html复制<div class="filter-buttons">
<button class="filter-btn active" data-category="all">全部</button>
<button class="filter-btn" data-category="fruit">水果</button>
<button class="filter-btn" data-category="vegetable">蔬菜</button>
</div>
<div class="product-list">
<div class="product-item" data-category="fruit">
<h3>苹果</h3>
<p>¥5.00 / 斤</p>
</div>
<div class="product-item" data-category="vegetable">
<h3>番茄</h3>
<p>¥3.50 / 斤</p>
</div>
<!-- 更多商品卡片 -->
</div>
<p class="result-count">共 <span id="count">0</span> 件商品</p>
这里的核心思路是:把分类信息放在 data-category 属性上,点击按钮时读取按钮上的 data-category 值,然后遍历所有商品卡片,通过比较分类值来决定显示还是隐藏。这种设计的好处是数据与展示分离,你不需要维护额外的 JS 数组,直接读取 DOM 上的属性即可。
3.2 完整功能实现与代码解读
javascript复制$(function () {
// 点击筛选按钮
$('.filter-btn').on('click', function () {
// 获取当前点击按钮的分类值
var category = $(this).data('category');
// 切换按钮的高亮状态
$('.filter-btn').removeClass('active');
$(this).addClass('active');
// 遍历所有商品卡片
var visibleCount = 0;
$('.product-item').each(function () {
var itemCategory = $(this).data('category');
if (category === 'all' || category === itemCategory) {
$(this).show();
visibleCount++;
} else {
$(this).hide();
}
});
// 更新显示数量
$('#count').text(visibleCount);
});
});
这段代码里有两个关键点。第一个是 .on('click', fn),这是 jQuery 推荐的事件绑定写法,替代了废弃的 .click(fn),它可以接受更多参数来控制事件行为。第二个是 .each() 遍历方法,它遍历的是页面上所有匹配选择器的元素,函数里的 this 指向当前遍历到的那个 DOM 元素,所以需要用 $(this) 把它包装成 jQuery 对象才能调用 .show() 和 .hide() 方法。
这里补充一个细节:$(this).data('category') 读取的是 HTML 标签上 data-category 属性的值。data 属性是 HTML5 提供的自定义数据方案,格式是 data-xxx,jQuery 会自动忽略大小写问题,而且如果你的属性值包含数字,jQuery 会尝试将其转换成数字类型。比如 data-count="5",读取出来是数字 5 而不是字符串 "5"。这是 jQuery 的便利之处,但也要注意可能因此产生类型判断的不一致。
3.3 为什么用 show/hide 而不是直接操作 style.display
你可能会问:为什么不直接写 this.style.display = 'none' 或者 this.style.display = 'block'?因为 show/hide 方法会在显示时恢复元素原来的 display 值。假设一个商品卡片原本的 display 值是 inline-block,你用 this.style.display = '' 未必能恢复到原来的样式,而 .show() 会智能地帮你还原。这个细节在项目里真实出现过:一个有 inline-block 布局的列表,用原生 display 方式控制显隐,结果某些卡片显示后从横排变成了竖排,样式全乱了。用 jQuery 的 show/hide 就没有这个问题。
3.4 常见问题:点击事件在动态添加的元素上失效
这个案例还有一个非常值得展开的场景:如果商品列表是 AJAX 动态加载出来的,或者用户点击按钮后新增了一个商品卡片,那么直接写在 $('.filter-btn') 上的 click 事件对新增元素是不起作用的。因为事件绑定是在页面加载时就完成的,当时只绑定了已有的元素。
解决方案是使用事件委托:
javascript复制$(document).on('click', '.product-card', function () {
// 处理点击商品卡片的逻辑
});
事件委托的原理是:把事件绑定到父级元素上,利用事件冒泡机制,当子元素被点击时,事件会冒泡到父级,父级再判断事件源是不是符合选择器的元素。这样无论子元素是何时创建出来的,都能被捕获到。在实际项目中,动态加载内容的场景非常常见,事件委托是一个必须掌握的技巧。
4. 实战二:图片与二维码合成小工具的完整实现
4.1 需求还原,做一个可拖拽、可缩放、可导出的工具
搜索热词中有一条非常具体的需求:“jquery 展示一张图把二维码移动到这张图上可以调整二维码大小保存生成的新图片”。这其实就是一个小型的图片编辑工具:在一张背景图上,允许用户拖动一个二维码到任意位置,调整它的大小,最后把合成后的图片导出保存。这个需求在电商做海报、公众号做推广图时非常常见。
要实现这个功能,需要用到几个关键技术点:拖拽定位、大小调整、Canvas 图片合成、以及图片导出。jQuery 在这里负责简化 DOM 操作和事件处理,Canvas 负责最终的图像合成。
4.2 页面结构与基础样式
html复制<div id="editor">
<div id="bg-wrap">
<img id="bg-img" src="bg.jpg" alt="背景图">
<div id="qr-code">
<img src="qrcode.png" alt="二维码">
</div>
</div>
</div>
<button id="btn-download">保存图片</button>
css复制#bg-wrap {
position: relative;
width: 600px;
height: 400px;
border: 1px solid #ccc;
overflow: hidden;
}
#bg-img {
width: 100%;
height: 100%;
object-fit: cover;
}
#qr-code {
position: absolute;
top: 50px;
left: 50px;
width: 100px;
height: 100px;
cursor: move;
border: 2px dashed #00aaff;
}
#qr-code img {
width: 100%;
height: 100%;
}
这里的关键是把二维码的定位方式设置为绝对定位,并相对于背景图容器定位。容器设置了 overflow: hidden,这样二维码就不会被拖到容器外面去。二维码的初始尺寸通过 width/height 控制,天然为后面的缩放功能做好了准备。
4.3 拖拽功能的实现逻辑
拖拽的核心思路是:在二维码元素上监听 mousedown,记录鼠标按下时的位置和二维码当前的 left/top 值;然后在 document 上监听 mousemove,计算鼠标移动的位移差,并更新二维码的位置;最后在 mouseup 时解除移动监听。
javascript复制$(function () {
var $qr = $('#qr-code');
var isDragging = false;
var startX, startY, originX, originY;
$qr.on('mousedown', function (e) {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
originX = parseInt($qr.css('left'));
originY = parseInt($qr.css('top'));
$(document).on('mousemove', onMouseMove);
$(document).on('mouseup', onMouseUp);
e.preventDefault();
});
function onMouseMove(e) {
if (!isDragging) return;
var dx = e.clientX - startX;
var dy = e.clientY - startY;
$qr.css('left', originX + dx + 'px');
$qr.css('top', originY + dy + 'px');
}
function onMouseUp() {
isDragging = false;
$(document).off('mousemove', onMouseMove);
$(document).off('mouseup', onMouseUp);
}
});
这里的几个关键决策:第一,移动事件绑定在 document 上而不是二维码元素上。因为在快速拖动时,鼠标可能移出二维码的范围,如果只在二维码上监听 mousemove,拖动就会断断续续。第二,e.preventDefault() 是为了防止选中文本或者图片默认拖拽行为造成页面抖动。第三,记录初始位置是必须的,否则每次移动都会在当前位置上叠加位移,导致二维码越拖越快。
4.4 缩放功能的实现思路
缩放功能常见做法是在二维码右下角放一个缩放手柄,按住手柄拖动时,根据鼠标位移改变二维码的宽高。为了保持等比缩放,需要同时调整宽度和高度。
html复制<div id="qr-code">
<img src="qrcode.png" alt="二维码">
<div id="resize-handle"></div>
</div>
javascript复制var isResizing = false;
var startW, startH, startX, startY;
$('#resize-handle').on('mousedown', function (e) {
isResizing = true;
startW = $qr.width();
startH = $qr.height();
startX = e.clientX;
startY = e.clientY;
$(document).on('mousemove', onResizeMove);
$(document).on('mouseup', onResizeUp);
e.preventDefault();
e.stopPropagation();
});
function onResizeMove(e) {
if (!isResizing) return;
var dx = e.clientX - startX;
var newSize = startW + dx;
if (newSize < 30 || newSize > 300) return; // 限制最小和最大尺寸
$qr.width(newSize);
$qr.height(newSize);
}
注意缩放手柄上的 mousedown 必须调用 e.stopPropagation(),阻止事件冒泡到二维码元素上,否则按住缩放手柄时,拖拽逻辑也会触发,两个操作会打架。
4.5 Canvas 合成与图片导出
拖拽和缩放都是在浏览器界面上的交互操作,要生成新图片,必须用 Canvas 把背景图和二维码绘制到同一个画布上。核心思路:创建一块和背景图同尺寸的 Canvas,先把背景图画上去,再根据二维码在容器中的位置和大小,计算它在背景图坐标中的位置,然后绘制二维码。
javascript复制$('#btn-download').on('click', function () {
var bgImg = document.getElementById('bg-img');
var canvas = document.createElement('canvas');
canvas.width = bgImg.naturalWidth;
canvas.height = bgImg.naturalHeight;
var ctx = canvas.getContext('2d');
ctx.drawImage(bgImg, 0, 0);
var bgWrap = document.getElementById('bg-wrap');
var wrapWidth = $(bgWrap).width();
var wrapHeight = $(bgWrap).height();
var qrLeft = parseFloat($qr.css('left'));
var qrTop = parseFloat($qr.css('top'));
var qrWidth = $qr.width();
var qrHeight = $qr.height();
// 计算二维码在 canvas 画布上的位置(按比例换算)
var canvasQrLeft = qrLeft / wrapWidth * canvas.width;
var canvasQrTop = qrTop / wrapHeight * canvas.height;
var canvasQrWidth = qrWidth / wrapWidth * canvas.width;
var canvasQrHeight = qrHeight / wrapHeight * canvas.height;
var qrImage = new Image();
qrImage.onload = function () {
ctx.drawImage(qrImage, canvasQrLeft, canvasQrTop, canvasQrWidth, canvasQrHeight);
// 导出图片
var link = document.createElement('a');
link.download = 'poster.png';
link.href = canvas.toDataURL('image/png');
link.click();
};
qrImage.src = $qr.find('img').attr('src');
});
这里有一个非常关键的换算逻辑:页面上容器显示宽度是 600px,背景图的原始尺寸可能是 1200x800,二维码在页面上 left 是 50px,那么在 Canvas 上就要按比例放大到 100px。如果不做这个换算,导出的图片会出现二维码位置错乱、尺寸不正确的问题。全部换算完毕后,用 canvas.toDataURL() 生成图片数据,再创建 a 标签触发下载。
4.6 跨域图片导致 Canvas 被污染的坑
使用 Canvas 合成图片时,最常遇到的问题是跨域图片导致画布被“污染”。如果背景图或二维码来自不同的域名,没有允许跨域访问,canvas.toDataURL() 会抛出 SecurityError。解决方案是在图片标签上添加 crossorigin 属性,同时服务端需要返回 CORS 头。
javascript复制var qrImage = new Image();
qrImage.crossOrigin = 'anonymous';
qrImage.src = $qr.find('img').attr('src');
在本地测试时,直接从 file:// 协议打开 HTML 文件也可能会遇到这个问题,建议你启动一个本地服务器来测试。Mac 上可以运行 python3 -m http.server 8080,Windows 可以用 VS Code 的 Live Server 插件。这也是一个非常实用的经验:很多 Canvas 功能必须通过 HTTP 协议打开页面才能完整工作。
5. 常见报错与排查技巧实录
5.1 jQuery 未加载:$ is not defined
这是 jQuery 开发中最常见的报错,没有之一。原因一般有两个:一是忘了引入 jQuery 文件;二是引入顺序错了,比如自己的 JS 文件在 jQuery 之前加载。解决方法是检查 script 标签的顺序,确保 jQuery 在业务 JS 之前。
html复制<!-- 先用这个 -->
<script src="jquery.min.js"></script>
<script src="my-code.js"></script>
<!-- 不要这样 -->
<script src="my-code.js"></script>
<script src="jquery.min.js"></script>
这个错误的出现场景非常多,而且不止新手会踩。我在做项目迁移时,曾经因为一个全局的脚本合并配置,导致 jQuery 的加载被放到最后,整站功能全部失效。排查时要先看控制台的报错信息,再确认 script 请求是否成功返回 200。
5.2 javascript:void(0) 和 javascript:void(0) 报错
热搜词里有不少关于 javascript:void(0) 的记录。这个写法一般出现在 href 属性中,用来阻止链接的默认跳转行为,比如 <a href="javascript:void(0)">点击</a>。但有时它会报错,原因可能是在 CSP(内容安全策略)环境下列为不安全内容,或者是某些浏览器插件拦截了这种协议。
我强烈建议不要再写 javascript:void(0),完全可以用现代写法替代:
html复制<a href="#" onclick="handleClick(); return false;">点击</a>
<!-- 更好的方式:不指定 href,也不写内联事件 -->
<a href="javascript:void(0)" class="js-handle">点击</a>
javascript复制$('.js-handle').on('click', function (e) {
e.preventDefault();
// 逻辑处理
});
5.3 函数在点击时立即执行
很多时候新手会写出这样的代码:
javascript复制$('#btn').click(handleClick()); // 注意这里有括号
function handleClick() {
// 逻辑
}
因为加了括号,handleClick() 会在页面加载时立即执行,而点击按钮时反而不执行任何逻辑。正确写法是去掉括号:$('#btn').click(handleClick)。这是一个非常细节的问题,但出现频率极高。如果你点击按钮发现没有反应,第一时间检查事件绑定处是否加了括号。
5.4 对象转字符串出现 [object Object]
当你执行 console.log('结果:' + data) 时,如果 data 是一个对象,控制台输出的是 [object Object]。这在 AJAX 请求返回数据后特别常见。解决办法是用 JSON.stringify 转换成字符串,或者直接在控制台里只输出 data,让浏览器以对象形式展示。
javascript复制console.log(data); // 输出完整对象
console.log(JSON.stringify(data)); // 输出 JSON 字符串
5.5 快速排查技巧速查表
| 报错现象 | 可能原因 | 排查方向 |
|---|---|---|
| $ is not defined | jQuery 未引入或顺序错误 | 检查 script 加载顺序 |
| Element is not defined | 元素变量名写错或未声明 | 检查 ID 和变量名 |
| 点击按钮无任何反应 | 函数加了括号、事件绑定在动态元素上失效 | 检查事件绑定语法、考虑用事件委托 |
| canvas.toDataURL() 报 SecurityError | 图片跨域导致画布被污染 | 添加 crossorigin 属性、检查服务端 CORS |
| [object Object] | 直接拼接了对象 | 用 JSON.stringify() 或 console.log 单独输出 |
| 页面空白无报错 | JS 语法错误导致脚本未执行 | 打开控制台查看是否有红字报错 |
5.6 jQuery 的隐式迭代特性
还有一个非常容易忽略的特性,jQuery 的隐式迭代。当你使用 $('.product-item').hide() 时,这个操作会自动应用于所有匹配的元素,不需要手动写循环。这在处理批量操作时非常方便。但也要注意,在读取属性时,隐式迭代并不会返回所有元素的值,只会返回第一个元素的值。
javascript复制// 设置操作:作用于所有匹配元素
$('.item').css('color', 'red');
// 读取操作:仅返回第一个元素的值
var color = $('.item').css('color'); // 只有第一个 item 的颜色
这个特性在实战中非常实用。比如要给一组按钮统一绑定事件,直接用 $('.btn').on(...) 就能批量完成,不需要 each 循环。但如果你要读取每个元素的值并进行不同处理,还是需要 each 遍历。
6. 原生 JS 与 jQuery 的共存与取舍
6.1 现在还需要学 jQuery 吗
很多新学者会问:现在 React / Vue 这么流行,jQuery 是不是已经过时了?我的回答是:jQuery 的核心思想不会过时,而且你翻开源码会发现,它的很多封装方式至今影响着现代前端库的设计。
具体来说,如果你只做现代前端项目,直接学框架没问题。但以下几个场景,jQuery 仍然有不可替代的价值:老项目维护、WordPress 主题开发、需要兼容旧浏览器的企业系统、以及一些不需要复杂状态管理的中小型页面。而且 jQuery 的 API 设计非常优秀,学完它,你对 DOM 操作的理解会更扎实,再学框架时反而更容易理解虚拟 DOM 和响应式设计解决了什么问题。
6.2 原生 JS 和 jQuery 混合使用的注意事项
在实际项目中,你可能会遇到 jQuery 和原生 JS 混用的情况。一个核心注意事项是 jQuery 对象和原生 DOM 对象不是同一个东西,方法不能混用。
javascript复制// 错误混用
$('#btn').innerHTML = '新文本'; // 报错:$(...).innerHTML is not a function
// 正确方式一:用 jQuery 方法
$('#btn').html('新文本');
// 正确方式二:先取出原生 DOM 元素
document.getElementById('btn').innerHTML = '新文本';
// 或者
$('#btn')[0].innerHTML = '新文本';
jQuery 对象本质上是一个类数组结构,里面装着原生 DOM 元素。你通过 $('#btn')[0] 或 $('#btn').get(0) 就能拿到原生的 DOM 对象,然后就可以使用原生属性了。理解了这一点,很多奇怪的报错都能迎刃而解。
6.3 从 jQuery 的思路理解前端框架
jQuery 把 DOM 操作封装得非常好,但你依然需要手动维护页面状态:筛选列表里的选中分类、图片素材当前的位置、表单里的数据等等。当页面复杂度上升到一个程度,手动维护 DOM 和状态的一致性就成了灾难。前端框架的核心价值,正是把这个“数据 -> 视图”的同步过程自动化。
但这不代表 jQuery 的实战经验白学了。相反,正因为你写过 jQuery,知道手动操作 DOM 有多繁琐,才能真正理解 React 的虚拟 DOM、Vue 的模板编译这些设计到底解决了什么问题。我见过不少直接学框架的新手,写出来的代码经常误用 ref 直接改 DOM,就是因为缺少 jQuery 阶段对“数据驱动视图”的理解——你明白了“为什么要改变”,才会去用框架的响应式方案,而不是绕回手动操作 DOM 的老路。
我在实际维护项目的过程中,一个很深的体会是:jQuery 的 API 命名极其符合直觉,show()、hide()、append()、remove(),每个方法的名字就是它做的事情。这种“命名即文档”的设计,放在今天依然值得学习。而且网上大量老接口文档、技术博客都是基于 jQuery 写的,你完全看得懂,这份能力的复用价值远超预期。初学阶段多花点时间把基础打牢,后面学什么框架都会轻松很多。
