我最早开始学前端的时候,读到的入门路径几乎都是“HTML + CSS + JavaScript”三件套,然后再加一个 jQuery。HTML 负责页面结构,CSS 负责视觉表现,JavaScript 负责交互和逻辑,jQuery 则是那个年代让 JavaScript 写起来顺手一点的老帮带。哪怕现在框架满天飞,原生 JS 的能力也比以前强了很多,jQuery 依然大量存在于企业老项目、后台管理系统和一些外包代码里。这篇是“快速入门”系列的第三篇,标题里的 JS 与 jQuery 实战,其实就是把前面两篇积累下来的结构知识,真正变成能用的页面交互能力。适合已经能看懂 HTML/CSS、但一写 JS 就发懵的人,也适合学过语法但不知道怎么拼出一个完整功能的人。这篇不做漫天飞舞的原理背诵,只讲我实际带新手时反复提到的知识点和踩过的坑。
1. 整体设计与思路拆解
1.1 为什么先把原生 JavaScript 放在前面
很多零基础学员问过我一个问题:既然 jQuery 写起来更短,为什么不直接学 jQuery,遇到不想写的原生语法就绕过去?我的答案是:jQuery 只是工具,它擅长的东西集中在选择器、DOM 操作、事件绑定和 AJAX 上,而实际项目里真正难住你的从来不是工具方法记不记得住,而是你对 JavaScript 本身的数据类型、作用域、异步回调有没有概念。
举个例子。jQuery 里 $('#box').hide() 确实一句话就能把元素藏起来,但如果你不理解 $() 返回的其实是一个 jQuery 对象,不理解它和原生 DOM 对象之间怎么转换,后面一旦遇到 $(this) 指向混乱、用 .each 遍历完数据不会渲染到页面上的问题,你会无从下手。反过来,当你理解了原生 document.querySelector('#box') 拿到的是什么,理解了数组和对象的操作方法,再回头用 jQuery,会觉得很多 API 都是“本来就是如此”。
所以我设计的这一篇,核心思路是双线并进:先花一大部分把 JS 的高频场景讲透,再切换到 jQuery 实战,把同样的需求分别用原生做一次、用 jQuery 做一次。你看到两者在写法上的差异,也就明白 jQuery 这么多年一直被怀念的原因,同时不会因为依赖它而丢掉看现代代码的基本功。
1.2 动手前你最好已经具备的底子
这篇虽然是“快速入门”系列,但不是真正的零基础文章。我建议你在读之前,已经认识 HTML 里的常用标签,比如 div、span、ul、li、input、img;会一点点 CSS 选择器和基本盒模型,比如 class 和 id 的使用;最好还知道怎么在浏览器里按 F12 打开开发者工具。如果你前面的基础不太扎实,也可以顺手上网搜一下“HTML 基础标签”“CSS 选择器”快速补一下,不需要多深,懂大概意思就能跟得下来。
为什么我会强调这些?因为第三篇的内容开始涉及“把一个页面做活”的环节。你写 JS 时面对的不再是一堆静态标记,而是已经有结构的真实页面。如果连元素位置、父子关系都看不明白,后面操作 DOM 的时候很容易迷失。另外,建议准备一个本地编辑器和浏览器,我写的示例代码不要干看,最好亲自复制、改一改、跑一跑。编程和游泳一样,看一百遍讲解,不如自己呛一口水记性好。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JavaScript 核心语法与高频场景
2.1 数组增删与判空,别再看着一个空数组发懵
javascript 数组应用 是新手遇到的最频繁场景,也是最容易写出隐患的地方。很多从后端转过来的朋友,刚开始总会把数组当成一个“可以随便存东西的盒子”,但实际用起来经常会犯一个错:不判断就直接取数据。
先看一个最常见的写法:
javascript复制var arr = [];
arr.push('苹果');
arr.push('香蕉');
if (arr.length > 0) {
console.log('数组里有数据:', arr);
} else {
console.log('数组为空');
}
这个写法里最关键的一行就是 arr.length > 0。为什么不能直接 if (arr)?因为空数组在 JavaScript 里是 truthy 值,也就是说 if (arr) 永远为真,根本判断不出空不空。用 arr.length 才是判断数组里有没有数据的正确姿势。
还有一种情况是从接口拿到的数据不确定是不是数组,直接 arr.length 可能会报 undefined。这时候更安全的写法是:
javascript复制var data = getData(); // 可能返回数组,也可能返回 null/undefined
if (Array.isArray(data) && data.length > 0) {
// 再去做渲染操作
}
Array.isArray 是原生 JS 判断数组的稳妥方式,jQuery 里也能用 $.isArray,作用差不多。我个人建议新代码都优先用原生 Array.isArray,因为兼容性已经很好了。
数组的增删也值得单独说一遍。除了 push 从尾部加、pop 从尾部删,还有 unshift 从头部加、shift 从头部删。这里经常有人记混,我自己的记忆方法很简单:push 和 pop 是“屁股后面操作”,unshift 和 shift 是“头顶上操作”,字母多的 unshift 和 shift 都在头。
另外,如果你用 jQuery 从页面里选了一组元素,比如 $('li'),想判断页面上有没有 li 元素,注意这里经常有人拿 jQuery 对象直接用 if ($('li')),结果永远为真。正确做法是:
javascript复制if ($('li').length > 0) {
// 页面里有 li
}
这个和数组判空的思路完全一致,本质都是判断“数量”而不是“对象是否存在”。
2.2 函数、作用域与事件绑定
JavaScript 的函数有一个容易让新人懵的特性:函数是一等公民。它既可以被赋值给变量,也可以作为参数传给另一个函数,还可以在另一个函数里被 return 出来。这和 CSS 里那种“写一个类名然后等待被应用”的思维完全不一样。
举个例子:
javascript复制function sayHello(name) {
return '你好,' + name;
}
var greet = sayHello; // 函数赋值给变量
console.log(greet('小明')); // 你好,小明
function runWithName(name, fn) {
console.log(fn(name));
}
runWithName('小红', sayHello); // 你好,小红
这里最难理解的是 greet = sayHello 这行,它没有加括号。加括号是“调用函数”,不加括号是“把函数本身当作值传过去”。很多同学在写 setTimeout 的时候容易写错:
javascript复制setTimeout(sayHello, 1000); // 正确,一秒后调用 sayHello
setTimeout(sayHello('小明'), 1000); // 错误,这里会立即执行,而不是等一秒
事件绑定也一样,你绑定的永远是函数本身,不是函数执行的结果。下面这个写法很典型:
javascript复制document.getElementById('btn').addEventListener('click', function () {
alert('我被点击了');
});
function () {} 匿名函数在这里的作用,是把“点击之后要做什么”打包成一段逻辑,等到真正触发事件时才执行。你在调试时如果发现事件不响应,第一步就去看是不是不小心在函数名后面加了括号。
作用域也是新手重灾区。用 var 声明的变量是函数作用域,用 let 和 const 是块级作用域。以前 jQuery 时代大量项目用 var,所以你会看到很多闭包里藏着变量互相覆盖的问题。我自己调试时最常用的一招:如果页面上有两个同名的全局变量,后面声明的会覆盖前面的,极难排查。所以现在写新代码我都建议用 let 和 const,能少掉一半这种坑。
2.3 javascript:void(0) 的来历和报错真相
打开一些老代码,经常能看到 <a href="javascript:void(0)" onclick="doSomething()">点击</a>。新手第一次见到这串东西通常会懵:href 后面怎么不是网址?这其实是早年很流行的一种写法,目的是让链接点击后不跳转、不刷新页面,而是执行 JS 逻辑。
void 是 JavaScript 的一个运算符,后面跟任何表达式,它都返回 undefined。所以 void(0) 就是计算 0 然后返回 undefined,写进 href 里之后,浏览器点击链接时相当于执行了一段最终结果是 undefined 的 JS,于是页面就不会发生跳转。
但这里有个大坑,很多 HTML 模板或手滑代码里会出现 href="javascript:void(o)",看起来和 void(0) 只差一个字符,结果点击后控制台直接报错,提示 o is not defined。原因很简单:void(o) 里的 o 会被当成变量去解析,如果页面没有声明过变量 o,就会抛 ReferenceError。还有人把 0 打成了字母 o,这个问题肉眼很难发现,尤其是字体比较圆润的情况下。
我的建议是:新写的代码尽量不要再依赖 javascript: 协议。要实现“点击不跳转但能执行逻辑”,可以直接给链接绑定事件,并在事件回调里用 event.preventDefault() 阻止默认行为:
html复制<a href="#" id="link">点击我</a>
javascript复制document.getElementById('link').addEventListener('click', function (event) {
event.preventDefault();
// 做你想做的事情
});
这样代码语义更清楚,也不会碰到 void(o) 这种缺胳膊少腿的报错。如果你还是想沿用老写法,请务必确保写的是数字 0,不是字母 o。
3. jQuery 实战:选择器、创建标签与遍历
3.1 选择器与链式调用
jQuery 当年最吸引人的一点,就是选择器太方便了。原生 JS 里要写 document.querySelectorAll('.box'),jQuery 里一句 $('.box') 就搞定。它内部用的是 CSS 选择器语法,所以你会 CSS,基本就会 jQuery 的选元素。
这里要记住一个观念:$('.box') 返回的不是一个元素,而是一个 jQuery 对象。这个对象内部可能包含了多个 DOM 元素,也可能一个都没有,但你调方法时不需要一一点名。
链式调用是 jQuery 的一个特色写法。比如下面这个:
javascript复制$('#info')
.css('color', 'red')
.text('内容已更新')
.show();
这样写,每一行的返回值都是同一个 jQuery 对象,所以可以不断往后接方法。它的好处是代码紧凑,读起来像是在按顺序描述“把文字变红、改内容、显示出来”,非常直观。配合 $(this) 使用的时候要特别注意,$(this) 在事件回调里指向的是触发事件的元素,但如果你把它放在嵌套函数里,可能就变了。我见过很多新手在 .each 里直接写 $(this),结果拿到的是循环里的当前项,不是外面想要的元素,排查半天才发现作用域变化了。
3.2 创建标签时三种常见姿势
jQuery 动态创建标签也是一个高频场景,最常出现在“需要根据数据生成列表”的时候。第一种姿势是直接写 HTML 字符串:
javascript复制var item = '<li class="list-item">' + title + '</li>';
$('#list').append(item);
这种写法最简单,但如果 title 是用户输入的,就很容易引入 XSS 问题,后面我会专门讲。第二种姿势是用 jQuery 的属性对象写法:
javascript复制$('<li>', {
'class': 'list-item',
'text': title,
'css': { 'color': '#333' }
}).appendTo('#list');
这样写的好处是,text 会被当作文本内容插入,而不是被当成 HTML 解析,安全性好很多。第三种姿势是先创建一个 jQuery 对象,再像操作页面元素一样操作它:
javascript复制var $li = $('<li>');
$li.addClass('list-item');
$li.text(title);
$('#list').append($li);
三种姿势没有绝对的好不好,关键看你有没有不可信内容。凡是涉及用户输入的,我强烈建议至少用 text() 而不是拼接字符串。如果你确实需要复杂的 HTML 结构,可以考虑用模板字符串配合转义函数来做双重保险。
3.3 用 $.each 遍历数据并渲染列表
jQuery 的 $.each 是一个迭代工具,很多人把它和 JavaScript 原生的 forEach 混为一谈。两者确实很像,但 $.each 既可以遍历数组,也可以遍历对象,而且回调函数的参数顺序是固定的 (索引/键, 值):
javascript复制var fruits = ['苹果', '香蕉', '橙子'];
$.each(fruits, function (index, item) {
console.log(index + ': ' + item);
});
如果是对象:
javascript复制var user = { name: '小明', age: 20, city: '上海' };
$.each(user, function (key, value) {
console.log(key + ' = ' + value);
});
$('li').each 的写法也常见,它遍历的是 jQuery 对象里的每一个 DOM 元素,回调里 this 指向当前 DOM 元素,$(this) 才是指向当前元素的 jQuery 包装对象。
实际开发中最常用的组合,是把数组数据 $.each 循环一遍,然后动态创建标签插入到页面。比如把一列表格数据渲染成 <ul> 列表:
javascript复制var list = [
{ name: '洗衣机', price: 1999 },
{ name: '冰箱', price: 2999 },
{ name: '空调', price: 3999 }
];
var html = '';
$.each(list, function (index, item) {
html += '<li>' + item.name + ' - ' + item.price + '元</li>';
});
$('#productList').html(html);
这里要注意,每次循环都用了字符串拼接,频繁拼接大数据量时性能不好,更推荐先建一个数组,循环完了再 join 成字符串一次性插入:
javascript复制var items = [];
$.each(list, function (index, item) {
items.push('<li>' + item.name + ' - ' + item.price + '元</li>');
});
$('#productList').html(items.join(''));
这两个写法在数据少的页面上差别不大,但养成“不要频繁操作 DOM”的习惯,对以后写复杂页面很有帮助。
4. 综合实战:把二维码拖到一张图上再导出新图片
4.1 需求拆解:为什么这是个经典综合题
我在网上看到过这样一个提问:jQuery 展示一张图片,然后把二维码移动到这张图上,可以调整二维码大小,最后保存生成的新图片。这个问题看起来简单,但它把 DOM 操作、事件绑定、画布导出全串起来了,非常考验基本功。
拆解下来,需求其实有三个环节:
- 把二维码定位到底图上,并且能用鼠标拖动。
- 能调整二维码的大小。
- 把底图和二维码合成一张图片,并保存下载。
这三个环节分别是:绝对定位与拖动事件、范围滑块调整尺寸、canvas 画布合成导出。下面我来写一个能直接运行的简化版本。
4.2 拖拽定位与缩放的实现
先把页面结构搭出来:
html复制<div id="stage">
<img id="bg" src="bg.jpg" alt="底图">
<img id="qrcode" src="qrcode.png" alt="二维码">
</div>
<div>
<label>大小:<input type="range" id="sizeRange" min="40" max="200" value="80"></label>
</div>
<button id="saveBtn">保存图片</button>
CSS 部分要让舞台容器是相对定位,二维码是绝对定位:
css复制#stage {
position: relative;
width: 600px;
height: 400px;
border: 1px solid #ddd;
overflow: hidden;
}
#bg {
width: 100%;
height: 100%;
}
#qrcode {
position: absolute;
left: 240px;
top: 140px;
width: 80px;
height: 80px;
cursor: move;
}
拖拽的核心思路:在二维码上按下鼠标时,记录鼠标相对于二维码左上角的位置;拖动过程中,不断计算二维码新的 left 和 top;松开鼠标时删掉事件监听,防止拖拽结束后还在乱跳。
javascript复制var $qrcode = $('#qrcode');
var isDragging = false;
var offsetX = 0;
var offsetY = 0;
$qrcode.on('mousedown', function (e) {
isDragging = true;
offsetX = e.clientX - $qrcode.offset().left;
offsetY = e.clientY - $qrcode.offset().top;
});
$(document).on('mousemove', function (e) {
if (!isDragging) {
return;
}
var newLeft = e.clientX - offsetX;
var newTop = e.clientY - offsetY;
$qrcode.css({ left: newLeft + 'px', top: newTop + 'px' });
});
$(document).on('mouseup', function () {
isDragging = false;
});
这里有几个细节容易踩坑。第一,offsetX 必须用 e.clientX - $qrcode.offset().left 计算,否则你一按鼠标,二维码会瞬间“跳”到鼠标指针下方,拖动体验非常突兀。第二,监听 mousemove 时尽量绑在 document 上,如果绑在二维码本身上,鼠标移出二维码区域时就会断掉,拖得不跟手。第三,如果页面有滚动条,直接 e.clientX - 容器左上角 的算法还要结合滚动偏移,我这里为了演示简化成相对文档坐标,实际项目里可以自行调整。
缩放部分用滑块控制二维码的宽高,同时要保持宽高比例一致:
javascript复制$('#sizeRange').on('input', function () {
var size = parseInt($(this).val(), 10);
$qrcode.width(size).height(size);
});
注意这里用了 input 事件而不是 change,原因是 input 会在拖动滑块的过程中持续触发,反馈更实时。
4.3 合成画布并保存导出
拖拽和缩放只在页面上生效,想要保存成新图片,需要把底图和二维码画到一个 canvas 上,再从 canvas 导出图片数据。
javascript复制$('#saveBtn').on('click', function () {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var bg = document.getElementById('bg');
var qr = document.getElementById('qrcode');
canvas.width = bg.width;
canvas.height = bg.height;
// 画底图
ctx.drawImage(bg, 0, 0, canvas.width, canvas.height);
// 画二维码
var qrX = $qrcode.position().left;
var qrY = $qrcode.position().top;
var qrSize = $qrcode.width();
ctx.drawImage(qr, qrX, qrY, qrSize, qrSize);
// 导出图片
var dataUrl = canvas.toDataURL('image/png');
var a = document.createElement('a');
a.href = dataUrl;
a.download = 'result.png';
a.click();
});
这个版本能很好地跑通整个流程,但有几个关键点我需要提醒你。
一是 canvas.width = bg.width。这里用的是图片原始尺寸,不是 CSS 显示尺寸。如果底图在页面里被 CSS 压缩成 600px,但原始文件是 1200px,你用 $('#bg').width() 取到的可能是 CSS 宽度,画出来的结果会跟预期不一致。建议直接取图片的 naturalWidth 和 naturalHeight,这是图片的固有尺寸。
二是二维码的位置。$qrcode.position() 返回的是相对于 offsetParent 的位置,如果你的舞台容器定位没错,这个值基本准确。但如果页面结构里出现了其他的 position: relative 祖先元素,position() 可能计算到错误的参照系。更稳妥的方式是用 getBoundingClientRect 结合舞台位置换算,不过对入门示例来说,position() 够用。
三是桌面端测试直接 a.click() 下载没问题,在部分移动端浏览器里可能被拦截,这时可以用 window.open(dataUrl) 或提示用户长按保存。我自己在移动端调试时踩过这个坑,下载函数永远没有反应,后来改成新窗口打开才解决。
4.4 顺手写一个“一键返回顶部”算法
HTML 里的“一键返回顶部”是另一个经久不衰的需求,看起来简单,但直接 window.scrollTo(0, 0) 会让页面瞬间跳上去,体验特别生硬。平时刷刷购物网站、阅读页,返回顶部基本都是平滑滚动的,实现方式也不复杂。
核心思路是不要一次跳到位,而是每隔一小段时间往顶部移动一段距离,速度逐渐变慢,产生一种“被吸回去”的感觉。
javascript复制function scrollToTop(duration) {
var currentY = window.pageYOffset || document.documentElement.scrollTop;
var start = null;
function step(timestamp) {
if (!start) {
start = timestamp;
}
var progress = timestamp - start;
var percentage = Math.min(progress / duration, 1);
// 减速缓动
var ease = 1 - (1 - percentage) * (1 - percentage);
window.scrollTo(0, currentY * (1 - ease));
if (percentage < 1) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
这段代码里最重要的是缓动公式 1 - (1 - p) * (1 - p),它能让滚动速度先快后慢。用 requestAnimationFrame 而不是 setInterval,是因为浏览器会在下一次绘制前自动调用回调,动画更流畅,也不容易堆积太多没执行的定时器。
如果你在一个老项目里不方便写这种异步逻辑,也可以用 jQuery 自带的动画方法,几行就搞定:
javascript复制$('html, body').animate({ scrollTop: 0 }, 500);
为什么选择器要同时写上 html 和 body?因为不同浏览器里能滚动的是不同元素,Chrome 下通常是 body,Firefox 和某些老版浏览器则是 html,两个都写才能保证兼容。这也是当年 jQuery 使用者们口口相传的一个经验。
5. 我踩过最多的高频坑
5.1 jQuery 中的 XSS 输出问题
jquery xss 这个话题,很多新人觉得和自己无关,直到某天在动态渲染用户输入时把页面搞崩了才追悔莫及。简单说,XSS 就是用户输入的内容里带有 HTML 或脚本,如果开发者不加处理地把它插入到页面里,浏览器可能会把这段内容当成 HTML 执行。
最常见的危险写法是:
javascript复制var userInput = '<img src=x onerror="alert(\'xss\')">';
$('#box').html(userInput);
html() 方法会解析 HTML 标签,用户输入里的 <img> 会被真正创建出来,于是 onerror 里的脚本就执行了。防止这类问题的基本手段是尽量使用 text() 而不是 html(),因为 text() 会把内容当作纯文本输出,标签会被原样显示,不会触发解析。
如果确实需要生成一段结构化的 HTML,比如后台返回了一段富文本,那就要在渲染前做转义,把 <、>、& 等字符转换成实体。jQuery 里没有内置万能的安全方法,很多老项目直接自己写一个 escapeHtml 函数来处理。如果你在维护老代码,看到用字符串拼接生成 HTML 的地方,第一步就要检查里面有没有用户可控的数据。
5.2 Flex 布局子元素宽度自适应的几个细节
现在写 CSS 布局,flex 已经是默认选择,但新手在“子元素宽度自适应”上频繁翻车。比较典型的问题是这样的:一个容器里有两个子元素,想让左边的固定宽度,右边的自动填满剩余空间,于是写了:
css复制.container {
display: flex;
}
.left {
width: 200px;
}
.right {
flex: 1;
}
大多数情况下这样就能工作,但如果右侧内容太长,你可能会发现子元素把容器撑破了,或者文字不换行。这时候最常用的一招是给右侧加上 min-width: 0。为什么?因为 flex 子项的 min-width 默认值是 auto,也就是不让内容被压缩到小于内容最小宽度,所以长单词或长 URL 会把布局撑开。
另外还要注意 gap 的兼容问题。以前在 flex 布局里给子元素之间加间距,我会用 margin 或者给子元素设置 + 选择器,现在 gap 已经得到了很好的支持,简洁很多:
css复制.container {
display: flex;
gap: 16px;
}
如果你还在维护一些旧项目,遇到老机型不识别 gap,备选方案就是给子元素加 margin-right,再用 :last-child 去掉最后一个的间距。还有个小程序特有的问题,苹果端底部会有一段白色安全区,做悬浮按钮时需要加 padding-bottom: constant(safe-area-inset-bottom) 和 env(safe-area-inset-bottom) 来适配,这也是我在实际项目里被用户反馈提醒才知道的。
CSS 字体渐变也是一个能提升观感的小技巧,代码并不复杂:
css复制.gradient-text {
background: linear-gradient(90deg, #ff6a00, #ee0979);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
关键在于 -webkit-background-clip: text 加上 color: transparent,让背景色只在文字形状内显示。如果你设置了 color: transparent 但背景没裁剪成功,文字就会消失,这是新手最容易困惑的联动问题。
5.3 常见报错与排查速查表
javascript 运行时报错 大概是最劝退新人的事情了。我见过的报错里,90% 都集中在几个固定类型上。这里整理成一张速查表,以后调试的时候可以直接对照:
| 错误信息 | 常见原因 | 排查思路 |
|---|---|---|
xxx is not defined |
变量或函数没声明 | 检查拼写、作用域、引入顺序 |
Cannot read property 'length' of undefined |
对一个 undefined 值取属性 | 先判断对象是否存在 |
Unexpected token |
语法错误,常见为少括号、少逗号 | 看报错行列,检查括号是否成对 |
$(...).xxx is not a function |
jQuery 方法名拼错或依赖库未加载 | 确认 jQuery 版本和插件引用顺序 |
Cannot set property 'innerHTML' of null |
试图往不存在的元素插入内容 | 确认 JS 是否在 DOM 加载后执行 |
Request failed |
AJAX 请求失败 | 看网络面板,检查接口地址和跨域设置 |
我特别想强调一下“JS 文件放在 head 里导致元素找不到”的问题。如果你把 <script> 写在 <head> 里,而 JS 代码直接操作了 body 里的元素,浏览器解析到这段 JS 时,body 还没加载完,所以 document.getElementById 会返回 null。解决方案很朴素:把 <script> 放到 body 结束标签之前,或者用 $(function () { ... }) 包裹代码,等 DOM 就绪以后再执行。
关于 JavaScript 环境配置 macOS,很多人会在 macOS 上配置 Node.js 环境时出错,本质上跟浏览器里的 JS 关系不大。如果你只是想跑前端网页,装一个现代浏览器就能开始;如果要跑 Node.js,去官网下载 LTS 版安装包,然后命令行执行 node -v 看有没有输出版本号即可。如果提示 command not found,多半是安装后没重启终端,或者 PATH 没包含 Node 的安装目录,重启一下大概率能解决。
6. 最后聊一点我的看法
带了不少零基础的朋友走过这条入门路线之后,我越来越确信一件事:JS 和 jQuery 不是“选一个学”的关系,它们是理解前端演进的两把钥匙。原生 JS 让你知道浏览器底层是怎么工作的,jQuery 让你看到封装和取舍的意义。我至今还在维护一些老项目,里面 jQuery 代码动辄上千行,如果当初没有认真花时间搞清楚选择器、事件绑定、DOM 操作这些基础,现在根本没法下手。
如果你刚开始学,我建议给自己安排一个小目标,不用贪大,做一个能跑的“待办列表”就够了。需要输入框新增内容、渲染列表、删除单条、标记完成,这套流程几乎覆盖了这篇讲到的所有核心点。做完之后,再把列表数据换成接口返回的 JSON,尝试用 $.each 渲染出来,你就已经把 HTML、CSS、JavaScript 和 jQuery 串成一条完整的线了。遇到报错不要慌,控制台里的红色信息不是洪水猛兽,它只是浏览器在告诉你下一步该检查哪里。多试几次,你也会慢慢习惯这种“写代码,看效果,修 Bug”的循环。
