学到这里,说明你已经跨过了 Day01 里“JS 是什么、变量怎么声明、数据类型有哪些”那个阶段,开始进入真正写逻辑的环节了。今天这份笔记是我自己在从 0 学 JS 过程中整理的第二天内容,核心会覆盖运算符、流程控制、函数、基础 DOM 操作、高频数组字符串方法,以及怎么用控制台调试代码。学完这些,你就能写出“点按钮页面变颜色”“往列表里加内容”这类最简单的交互页面,也为后面啃框架、写业务逻辑打底。
这篇 Day02 笔记适合两类人:一类是完全没有编程基础、正在按系列课往下走的纯新手;另一类是以前零散看过 JS 语法、但没动手写过完整逻辑的人。我不打算把 MDN 文档搬过来念,而是把每个知识点后面“为什么这么设计”和“实际会踩什么坑”一起讲清楚,这样你学完不是记住概念,而是真的能上手。
1. 运算符与类型转换:写代码最先遇到的坑都在这
1.1 算术运算符背后的隐式转换
Day01 里我们知道了 JS 有 number、string、boolean 这些数据类型,但真正写代码时,数据经常不按你设想的类型出现。比如你从输入框里拿到的值,永远是字符串,哪怕用户输入的是数字,这也是新手第一个崩溃点。看这段代码:
javascript复制let num1 = prompt("请输入第一个数字");
let num2 = prompt("请输入第二个数字");
console.log(num1 + num2);
如果你输入 5 和 3,控制台打印的不是 8,而是 "53"。因为 prompt 返回的永远是字符串,两个字符串用 + 连接,结果就是字符串拼接。这就是 JS 里最常见的隐式类型转换:当 + 两边有一边是字符串时,另一边会被转成字符串,然后拼接起来。
而 -、*、/ 这些运算符不一样,它们会把字符串往数字方向转,所以 "5" - 3 结果是 2。这种“看情况转换”的规则很灵活,但也容易埋雷。我的建议是:别依赖隐式转换,主动用 Number() 或 parseInt() 把类型转清楚。
javascript复制let num1 = Number(prompt("请输入第一个数字"));
let num2 = Number(prompt("请输入第二个数字"));
console.log(num1 + num2); // 8
1.2 == 和 === 必须分清楚
JS 里的相等判断有两个:== 会做类型转换后再比较,=== 要求类型和值都相等才返回 true。新手最容易踩的坑就是两个都用 ==。比如:
javascript复制console.log(0 == false); // true
console.log(0 === false); // false
console.log("" == 0); // true
console.log("" === 0); // false
原因在于 == 先把 false 转成 0,再把空字符串转成 0,所以这些看起来“不该相等”的值全相等了。实际开发中,有人会用这个特性写简化判断,但我的建议是新手一律用 ===,避免逻辑判断出诡异问题。你以后读别人的代码,如果看到 ==,要么是故意利用转换,要么就是老代码遗留问题。
1.3 逻辑运算符的短路行为
&& 和 || 这两个运算符,不只是“与”和“或”那么简单,它们有短路特性:&& 左边为 false 就直接返回左边,左边为 true 就返回右边;|| 左边为 true 就直接返回左边,否则返回右边。这意味着它们不一定返回布尔值,可能返回原始值。
javascript复制let name = "";
let displayName = name || "匿名用户";
console.log(displayName); // "匿名用户"
这种写法在项目里非常常见,用来给变量设置默认值。我在 Day02 阶段就把这个特性拿出来讲,是因为如果你不懂短路行为,看到别人代码里 a && b 这种写法会一头雾水。后面学到 React 或者 Vue 的模板渲染,也会经常看到 条件 && <组件> 这种模式,本质上就是短路逻辑在控制渲染内容。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 流程控制:if、for、while 怎么组合出逻辑
2.1 if 分支和条件表达式的优先级
学完运算符,紧接着就是分支结构。if 本身不复杂,难在条件表达式的组合上。比如判断一个人是否可以参加活动,条件是“年龄在 18 到 60 之间,或者是管理员”,写成代码就是:
javascript复制let age = 25;
let isAdmin = false;
if ((age >= 18 && age <= 60) || isAdmin) {
console.log("可以参加");
}
新手容易漏掉括号。&& 的优先级比 || 高,所以这里不用括号的话,表达式会先算 age >= 18 && age <= 60,再和 isAdmin 做 ||,其实结果一样。但可读性很差,而且一旦条件复杂就容易判断错。我的习惯是:只要表达式里有多个逻辑运算符,就显式加括号,给未来的自己省点脑细胞。
2.2 for 循环:从打印数字到遍历数组
for 循环的模板很固定:初始化变量、判断条件、更新变量。但真正练手时建议直接从数组遍历开始,而不是只打印数字。比如:
javascript复制let fruits = ["苹果", "香蕉", "橘子"];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
这里有个细节:循环条件里不要写 i <= fruits.length - 1,直接写 i < fruits.length 就好。因为数组索引从 0 开始,最后一个元素的下标是 length - 1。写 i < fruits.length 能天然避开“多循环一次导致打印 undefined”的经典 bug。
如果你想要索引和值一起处理,可以用 for...of 配合 entries():
javascript复制for (const [index, fruit] of fruits.entries()) {
console.log(index, fruit);
}
这个写法在只需要值的时候更简洁,但新手阶段我还是建议先把传统 for 练熟,因为你后面看老代码、写算法题,传统 for 出现的频率非常高。
2.3 while 和 do...while 的使用边界
while 适合“不知道要循环多少次、只知道什么时候停止”的场景。比如写一个数字猜谜游戏,用户不猜对就继续循环:
javascript复制let answer = 7;
let guess;
while (guess !== answer) {
guess = Number(prompt("猜一个数字"));
}
alert("猜对了");
do...while 和 while 的区别是:do...while 至少执行一次循环体。这个特性在“先执行再判断”的场景里很有用,比如用户输入校验前先弹一次输入框。日常业务里 do...while 用得比 while 少,但面试题里偶尔会问两者的区别,所以至少要知道它“至少跑一次”这个特点。
同时要注意死循环问题。写循环的时候,一定要确保循环体内有改变条件的语句,否则浏览器标签页直接卡死。我实际见过初学者写 while (true) 忘了写 break,结果整个页面白屏,只能强杀浏览器进程。这种问题排查起来并不难,但体验非常糟糕。
3. 函数:把逻辑封装成可复用的积木
3.1 函数声明、函数表达式和箭头函数
函数是 JS 里最重要的抽象工具。同样一个功能,可以用三种写法:
javascript复制// 函数声明
function add(a, b) {
return a + b;
}
// 函数表达式
const add = function(a, b) {
return a + b;
};
// 箭头函数
const add = (a, b) => a + b;
新手一开始最容易疑惑的是:这三种写法到底有什么区别?最核心的区别有两点:
第一,函数声明有“提升”(hoisting),你在声明之前调用它也不会报错;函数表达式和箭头函数不行,必须先定义再使用。第二,箭头函数没有自己的 this,它里面的 this 是定义时所在作用域的 this。第二点在处理事件回调、定时器时会特别重要,新手可以暂时不用深究,先记住“如果回调函数里用到了 this,别用箭头函数乱替换”就行。
3.2 函数参数:默认值、剩余参数
函数参数的细节比想象中多。一开始我写参数时以为只是占据位置,后来发现参数里的默认值、剩余参数都很有用。比如你希望参数没传的时候有个兜底值:
javascript复制function greet(name = "朋友") {
console.log("你好," + name);
}
greet(); // 你好,朋友
greet("小明"); // 你好,小明
再比如你希望函数接收任意数量的参数,用剩余参数 ...args 就能收集所有参数为一个数组:
javascript复制function sumAll(...numbers) {
let total = 0;
for (let num of numbers) {
total += num;
}
return total;
}
console.log(sumAll(1, 2, 3, 4)); // 10
我在这个阶段自己的体会是:函数参数是你和函数使用者之间的“约定”。参数越少、含义越明确,函数就越容易复用。不要一上来就设计一堆可选参数,除非需求真的很需要。
3.3 返回值与作用域
函数里没有 return,调用后得到的就是 undefined。这个看似简单,但我在实际排查别人的代码时发现,很多人写了函数忘了返回值,然后在外面拿着结果去操作,结果整个数据链路都断掉。写函数之前可以先问自己:这个函数是要计算一个结果,还是要执行一个操作?需要结果就 return,只是执行操作就不用。
作用域则是“变量能见范围”的问题。函数内部用 let 声明的变量,外面访问不到;函数内部可以访问外部变量,但同名变量会优先访问内部的。看这个例子:
javascript复制let message = "全局";
function showMessage() {
let message = "局部";
console.log(message);
}
showMessage(); // 局部
理解作用域是理解闭包的前置条件。Day02 不深入闭包,但至少要建立一个概念:JS 的变量不是“哪里都能访问”,而是有层级关系的。把作用域想象成楼层:一楼变量在一楼能访问,二楼也能访问,但二楼变量只能在一楼的房间里用,拿到二楼就不认识了。
4. 基础 DOM 操作:让页面根据你的代码动起来
4.1 获取元素:getElementById 与 querySelector
JS 在前端最常干的一件事就是操作 DOM,也就是改变页面上显示的内容或样式。第一步是找到你想操作的元素。最传统的方法是 document.getElementById,后来更推荐 querySelector,因为它支持 CSS 选择器语法,更灵活:
javascript复制let title = document.getElementById("title");
let firstButton = document.querySelector(".btn");
let container = document.querySelector("#app .card");
这里的 .btn 是 class 选择器,#app .card 是子孙选择器。如果你用 querySelectorAll,拿到的是一组元素,需要通过 forEach 或索引去操作:
javascript复制document.querySelectorAll(".card").forEach(function(card) {
card.style.color = "red";
});
这个阶段你还不用纠结哪种性能更好,先把语法写对、元素能拿到就行。
4.2 修改内容与样式:innerText、innerHTML、style
拿到元素之后,无外乎要做几件事:改文字、改 HTML、改样式。看一个完整示例:
html复制<p id="info">原始文字</p>
<button id="changeBtn">点我修改</button>
javascript复制let info = document.getElementById("info");
let changeBtn = document.getElementById("changeBtn");
changeBtn.addEventListener("click", function() {
info.innerText = "文字已经被修改了";
info.style.color = "green";
info.style.fontSize = "20px";
});
这里有个新手很容易踩的坑:innerHTML 和 innerText 都能改内容,但 innerHTML 会把字符串当 HTML 解析。如果你把用户输入的内容直接塞进 innerHTML,用户输入 <img src=x onerror=alert(1)> 这类内容,就可能触发安全问题,也就是常说的 XSS 攻击。我的建议是:默认情况下能用 innerText 就用 innerText,只有确实需要渲染 HTML 标签时才用 innerHTML,并且要对插入内容做处理,哪怕你现在写的是练习项目,也要养成这个习惯。
4.3 事件监听:click、input、keydown
addEventListener 是绑定事件的标准方式,比直接在 HTML 标签上写 onclick="..." 更灵活,也更推荐。同一个元素可以绑多个事件,也能在需要时移除事件:
javascript复制let btn = document.getElementById("btn");
function handleClick() {
console.log("按钮被点击了");
}
btn.addEventListener("click", handleClick);
除了点击事件,常用的还有输入框的 input 事件和键盘的 keydown 事件。input 事件可以实时拿到用户输入的内容,非常适合做搜索联想、表单实时校验:
javascript复制let searchInput = document.getElementById("search");
searchInput.addEventListener("input", function(event) {
console.log(event.target.value);
});
事件对象 event 里包含很多信息,比如 event.target 是触发事件的元素,event.key 是按下哪个键。这个阶段你不用背全部 API,先掌握 click、input、keydown 三个高频事件,足够做出一堆小练习了。
5. 高频数组与字符串方法:真正写业务时天天都在用
5.1 数组的增删改查:push、pop、shift、unshift、splice
数组方法多到让人眼晕,但入门阶段先把“增删改查”四类基础方法掌握就够了。push 在末尾添加,pop 删除末尾,unshift 在开头添加,shift 删除开头。注意这些方法很多会修改原数组,而不是返回新数组。
splice 是更强大的方法,可以同时完成删除、插入、替换:
javascript复制let arr = ["a", "b", "c", "d"];
arr.splice(1, 2); // 从索引1开始删2个
console.log(arr); // ["a", "d"]
arr.splice(1, 0, "x", "y"); // 从索引1插入
console.log(arr); // ["a", "x", "y", "d"]
很多新手会在这里混淆 splice 和 slice。slice 是提取部分元素并返回新数组,不修改原数组;splice 是直接改原数组。记法上可以这样想:splice 的 p 像是“破坏”,会改动原数组;slice 的 l 像是“切片”,切下来不动原来的。
5.2 map、filter、forEach:遍历数组的三个常用工具
这几个方法是前端业务里出现频率最高的一组。forEach 只遍历,不返回新数组;map 遍历并返回一个等长的新数组;filter 遍历并筛选出符合条件的元素,返回新数组。看例子:
javascript复制let numbers = [1, 2, 3, 4, 5];
let doubled = numbers.map(function(n) {
return n * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
let evenNumbers = numbers.filter(function(n) {
return n % 2 === 0;
});
console.log(evenNumbers); // [2, 4]
numbers.forEach(function(n) {
console.log(n);
});
map 和 filter 都不会修改原数组,所以如果你发现原数组变了,大概率是你没注意回调函数里直接改了对象属性。特别是数组里存的是对象时,map 返回的新数组里对象引用还是原来的对象,属性改动了会影响到原数组。这是新手面向对象编程之前最容易忽略的“引用传递”问题。
5.3 字符串方法:includes、replace、repeat
字符串方法同样常用。includes 判断字符串是否包含指定内容,返回布尔值;replace 替换匹配的文本;repeat 把字符串重复 n 次。看几个基本用法:
javascript复制let sentence = "我喜欢学习 JavaScript";
console.log(sentence.includes("JavaScript")); // true
console.log(sentence.replace("喜欢", "热爱")); // 我喜欢热爱学习 JavaScript
console.log("哈".repeat(3)); // 哈哈哈
注意 replace 只替换第一个匹配项,想要替换所有匹配项,得用正则表达式或者 replaceAll。字符串方法不会修改原字符串,因为字符串是不可变的,所有方法都会返回一个新字符串。
javascript复制let text = "cat dog cat";
console.log(text.replace("cat", "pig")); // pig dog cat
console.log(text.replaceAll("cat", "pig")); // pig dog pig
console.log(text); // cat dog cat
这个阶段学字符串方法,不需要背全量 API,但建议至少把 includes、startsWith、endsWith、slice、split、join 这些高频方法敲一遍。尤其是 split 和 join,一个把字符串变数组,一个把数组变字符串,数据清洗时几乎天天用。
6. 控制台调试与报错排查:不会调试等于白学
6.1 console.log 的正确用法与断点思维
很多初学者觉得调试就是“在代码里到处写 console.log”,这没错,但要注意打好“标记点”。我自己的习惯是:在函数入口先打印一次参数,确认数据有没有正确传进来;在关键计算后打印一次结果,确认逻辑分支走没走对;在出现异常的分支位置打印提示字符串,确认是哪个条件没满足。
javascript复制function calcTotal(price, count) {
console.log("参数:", price, count);
let total = price * count;
console.log("计算后:", total);
if (total > 100) {
console.log("满足满减条件");
total = total - 20;
}
return total;
}
在浏览器里按 F12 打开开发者工具,切到 Console 面板就能看到打印结果。除了 console.log,我还常用 console.table 来打印数组对象,格式清晰很多:
javascript复制let users = [
{ name: "张三", age: 20 },
{ name: "李四", age: 22 }
];
console.table(users);
6.2 看懂浏览器控制台的报错信息
新手遇到红色报错会慌张,其实报错信息是帮你定位问题的最好线索。常见的几类错误:
| 报错内容 | 常见原因 | 处理思路 |
|---|---|---|
Uncaught ReferenceError: xxx is not defined |
变量名写错或还没声明就使用 | 检查变量名拼写、声明顺序 |
Uncaught TypeError: Cannot read property 'style' of null |
元素没找到,得到 null 后继续操作 | 确认 id 或选择器是否正确、HTML 是否已加载 |
Uncaught SyntaxError: Unexpected token |
语法错误,比如少括号、多逗号 | 看报错提示的行号,检查那一行附近的符号 |
Infinity 或 NaN |
数字计算异常 | 检查数据是字符串还是数字,是否出现了除 0 |
第一类报错最常见。比如你写 let userName = "张三",后面却拼成了 userNmae,控制台直接告诉你没定义。解决方式很简单:看到报错先读一遍变量名,再看行号。第三类语法错误虽然看着吓人,但通常只是少个括号,结构补全后立刻解决。
null 的问题特别值得注意。很多新手在“页面还没加载完”的情况下就操作 DOM,导致拿不到元素。解决办法是把 script 标签放到 body 结束之前,或者用 DOMContentLoaded 事件确保页面解析完成后再执行 JS。
6.3 从只会看结果到会打断点调试
console.log 虽然方便,但打印多了反而看不清逻辑。浏览器控制台的 Sources 面板里可以打断点,让代码执行到指定行时停下来,你再一步步往下走,逐行观察变量值的变化,这个本事越早学会越省钱。
我自己的体会是:打断点调试最大的好处是强迫你看清每个步骤,而不是像 console.log 那样“打一堆、最后靠猜”。实操时,在浏览器里打开你的 HTML 文件,F12 切到 Sources,找到对应的 js 文件,在行号上点一下就能打断点,然后刷新页面,代码就会停在断点位置,右侧会显示当前作用域里的变量值。点“下一步”按钮,一行一行地走,很快就能找到逻辑错误发生在哪一步。
新手阶段不需要掌握所有调试技巧,但至少要知道:代码报错有具体的提示和行号,先读报错信息,再定位问题;代码不报错但结果不对,就要靠打断点或 console.log 逐步排查。把这两个思路学会,写代码的心态会稳很多。
7. 一点实操体会
Day02 的内容量比 Day01 明显涨了一截,因为从这一篇开始你不再是“认识变量”,而是真正开始“写程序逻辑”了。我自己学下来的感受是:运算符和隐式转换是第一个坎,函数是第二个坎,DOM 操作会让人觉得“原来 JS 是这么让页面动起来的”,而数组字符串方法则是以后写业务天天要用的“肌肉记忆”。
建议你把今天的内容拆成三块来练:第一块是把运算符和流程控制过一遍,在控制台里多算几组表达式,直到看到 == 和 === 不再犹豫;第二块是把函数写熟,试着把“求数组平均数”“翻转字符串”写成函数,参数和返回值都想清楚;第三块是做一个 10 分钟的小页面,比如一个点击按钮修改文字的卡片,把 DOM 操作和事件绑定的手感练出来。
最后分享一个我实际踩过的小坑:在写函数的时候,参数名不要用 name、id 这种过于通用的单词,因为它们在浏览器环境中可能和全局对象属性产生冲突。我一度用 name 当参数,结果在控制台里打出奇怪的值,排查了半天才发现是命名撞车。Day02 阶段你可能觉得这是小事,但越往后越会发现,变量命名干净,能帮你省下大把调试时间。今天的笔记就先到这,下一篇我们继续往数组进阶方法和对象方向推进。
