JavaScript学习笔记:3.控制流与错误处理
上一篇搞定了JS的“内功心法”(基础语法与数据类型),这一篇咱们来解锁JS的“实战技能”——控制流与错误处理。如果说变量和数据类型是JS的“砖瓦”,那控制流就是“建筑图纸”(决定代码执行顺序),错误处理就是“安全网”(防止代码崩掉)。今天就用接地气的例子,带你吃透这些核心知识点,从此写代码能“做对决策”,还能“从容兜底”~
一、块语句:给代码划清“独立空间”
块语句是JS里最基础的“代码容器”,用一对{}把多条语句包起来,就像给代码划了个“独立房间”。它通常和控制流语句(if、for、while)搭配使用,比如:
while(奶茶没喝完){吸一口;// 这整个{}就是块语句}但这个“房间”对不同变量的“约束力”不一样——这就是新手最容易踩的坑!
避坑重点:var vs let/const的“房间规则”
var:串门的“邻居”:用var声明的变量不受块语句约束,会“串门”到函数或全局作用域。比如:
var奶茶="全糖";{var奶茶="三分糖";// 同一个变量,覆盖了外面的值}console.log(奶茶);// 输出“三分糖”——var直接“串门”改了全局变量这就像你在自己房间换了衣服,结果邻居的衣服也跟着变了,离谱!
let/const:安分守己的“住户”:用let或const声明的变量是“块级作用域”,只在自己的“房间”里生效,不会打扰外面:
let奶茶="全糖";{let奶茶="三分糖";// 独立的局部变量}console.log(奶茶);// 输出“全糖”——外面的变量不受影响这才是正常的“房间规则”!所以记住:块语句里优先用let/const,彻底和var说再见~
二、条件语句:JS的“决策大脑”
条件语句让JS能“根据情况做选择”,就像你出门前看天气:下雨带伞,晴天戴帽。JS里有两种核心条件语句:if...else和switch,各有擅长场景。
1. if…else:灵活的“多岔路口”
if...else就像人生的“岔路口”,满足条件走一条路,不满足走另一条。语法很简单:
if(条件A){走A路;}elseif(条件B){走B路;}else{实在没路走,兜底方案;}但这里藏着两个“致命坑”,新手必栽!
坑1:假值的“伪装术”
JS里有6个“假值”,它们会被条件语句当成false处理,其余全是true:
false、undefined、null0、NaN- 空字符串
""
举个反例,你以为能判断“用户输入了内容”,结果栽了:
const用户名="";// 用户没输入,是空字符串if(用户名){console.log("欢迎登录");}else{console.log("请输入用户名");// 正确执行,因为""是假值}// 坑!如果用户输入0:const年龄=0;// 0是合法年龄,但却是假值if(年龄){console.log("年龄合法");}else{console.log("请输入合法年龄");// 错误执行!}避坑指南:如果要判断“是否存在值”,别直接用if(变量),要用精准判断:
if(年龄!==undefined&&年龄!==null){// 排除未定义和空值console.log("年龄合法");}坑2:条件里的“赋值陷阱”
千万别在if条件里写赋值语句(=),要写比较语句(===)!比如:
let喜欢奶茶=false;if(喜欢奶茶=true){// 这里是赋值,不是比较!console.log("给你买奶茶");// 会执行,因为赋值后喜欢奶茶变成true}这就像你本来不喜欢奶茶,别人硬塞给你一杯,然后说“你看你喜欢吧”——逻辑完全混乱!最佳实践:条件里只写比较表达式,赋值单独写。
2. switch:高效的“多选一菜单”
当有多个固定选项需要匹配时,switch比if…else更简洁,就像餐厅里的“套餐菜单”,选一个套餐就对应一套服务。语法:
switch(套餐类型){case"汉堡套餐":给汉堡;break;// 关键!选完就退出case"披萨套餐":给披萨;break;default:给默认小吃;// 没有匹配项时的兜底}核心坑:switch的“穿透效应”
如果忘记写break,JS会继续执行下一个case的代码,就像你点了汉堡套餐,服务员不仅给了汉堡,还自动加了披萨和小吃——完全超出预期!比如:
const水果="香蕉";switch(水果){case"香蕉":console.log("香蕉是$0.48一磅");// 没写break!case"樱桃":console.log("樱桃是$3.00一磅");break;}// 输出:香蕉是$0.48一磅 → 樱桃是$3.00一磅(穿透了!)避坑指南:除了故意利用穿透的场景(极少),每个case后面必须加break!另外,switch的匹配是严格相等(===),不会自动类型转换,比如case "1"和case 1是两个不同的匹配项。
三、异常处理:JS的“安全网”——与bug和平共处
写代码难免遇到bug(就像走路难免摔跤),异常处理就是给代码装“安全网”,让它摔得轻一点,甚至能爬起来继续走。JS的异常处理核心是try...catch...finally,再加上throw自定义错误。
1. 三兄弟分工:try→尝试,catch→补救,finally→兜底
这三个语句的关系就像“快递签收流程”:
try:尝试做一件事(比如签收快递)catch:如果出问题(快递损坏、丢件),就补救finally:不管成没成功(签没签收),必须做的事(比如关门、谢谢快递员)
举个完整例子:
function签收快递(快递状态){if(快递状态==="损坏"){thrownewError("快递损坏,无法签收");// 主动抛出错误(丢件了!)}return"签收成功";}try{// 尝试签收const结果=签收快递("损坏");console.log(结果);}catch(错误){// 出问题了,补救console.error("出错了:",错误.message);// 输出错误信息联系商家理赔();// 补救措施}finally{// 不管怎样都要做console.log("快递处理完毕,关门");// 必执行!}输出结果:
出错了:快递损坏,无法签收 快递处理完毕,关门2. throw:主动“喊救命”——自定义错误
throw就像你发现快递有问题时,主动喊“快递员,这里有问题!”。它可以抛出任意类型的值(字符串、数字、对象),但最佳实践是抛出Error对象,因为它自带name(错误类型)和message(错误信息),方便调试:
// 不推荐:抛出字符串throw"快递损坏";// 推荐:抛出Error对象thrownewError("快递损坏,无法签收");// name: "Error", message: "快递损坏..."还可以用更具体的错误类型,比如TypeError(类型错误)、RangeError(范围错误),让错误信息更精准:
function计算年龄(出生年份){if(typeof出生年份!=="number"){thrownewTypeError("出生年份必须是数字");// 类型错误}if(出生年份<1900||出生年份>2024){thrownewRangeError("出生年份范围必须是1900-2024");// 范围错误}return2024-出生年份;}3. finally的“霸道逻辑”:覆盖一切返回值
finally有个很“霸道”的特性:不管try或catch里有没有return,finally都会执行,而且如果finally里有return,会直接覆盖前面的返回值!比如:
function测试(){try{return"try的返回值";}catch(e){return"catch的返回值";}finally{return"finally的返回值";// 覆盖前面的返回值}}console.log(测试());// 输出:finally的返回值这就像你本来想签收快递(return try的结果),但最后不管怎样,都必须执行“关门”(finally的return),直接忽略了之前的想法。避坑指南:finally里尽量不要写return,它的核心用途是“释放资源”(比如关闭文件、断开网络连接),而不是返回值。
4. 错误处理的“避坑红线”
- 别“吞掉”错误:catch块里不要只写
console.log(e),要用console.error(e)(会格式化错误,方便调试),更不要空catch(catch(){}, 相当于把bug藏起来,永远找不到)。 - 精准捕获:不要用try包裹所有代码,只包裹“可能出错的部分”(比如网络请求、数据解析),否则会掩盖真正的问题。
- 释放资源:文件、网络连接、定时器等资源,一定要放在finally里释放,避免内存泄漏。
四、总结:控制流与错误处理的“核心心法”
- 块语句:用
{}划清作用域,let/const是块级作用域,var是“串门户”,坚决不用var。 - 条件语句:
- if…else:警惕假值陷阱,条件里不写赋值,复杂判断用块语句。
- switch:每个case必加break,用default兜底,匹配是严格相等。
- 异常处理:
- try:包裹可能出错的代码,不滥用。
- catch:精准处理错误,不吞错,用Error对象。
- finally:释放资源,不写return。
JS的控制流让代码“有逻辑、会决策”,错误处理让代码“抗造、不脆断”。这两个技能结合起来,你写的代码就从“勉强能跑”变成“稳健可靠”了~