告别 var,拥抱 let/const:一文彻底搞懂 JavaScript 变量与作用域

告别 var,拥抱 let/const:一文彻底搞懂 JavaScript 变量与作用域

文章目录

1. 引言

相信很多前端开发者在刚开始接触 JavaScript 时,都遇到过下面这个经典的“坑”:

for(var i =0; i <3; i++){setTimeout(()=> console.log(i),100);}// 期望输出:0, 1, 2// 实际输出:3, 3, 3

为什么?因为 var 没有块级作用域,导致循环结束后的 i 变成了 3。如果你不懂变量提升和作用域,这种 Bug 往往让人排查一整天。

ES2015(ES6) 引入了 letconst,彻底解决了这些历史遗留问题。同时,函数也得到了极大的增强,比如默认参数、剩余参数以及大名鼎鼎的箭头函数。

本文将带你彻底搞懂:

  1. 为什么我们要“枪毙” var
  2. letconst 到底有什么区别?
  3. 什么是 TDZ(暂时性死区)?
  4. 箭头函数的 this 到底指向谁?

2. 正文

2.1 var 的“三宗罪”

在 ES6 之前,声明变量只能用 var。它有三个著名的特性,经常让开发者踩坑:

  1. 没有块级作用域
    var 声明的变量,只有函数作用域全局作用域,没有块级作用域(即 {} 内部)。这就是开篇那个循环 Bug 的根源。

允许重复声明
你可以无意中多次声明同一个变量,导致旧变量被覆盖,且不报错。

var a =1;var a =2;// 居然不报错!

变量提升
你可以在声明之前使用变量,虽然它的值是 undefined,但这会引发逻辑混乱。

console.log(myName);// undefined,不会报错!var myName ="Jack";// 实际上 JS 把它变成了:// var myName;// console.log(myName);// myName = "Jack";

2.2 letconst 的正确打开方式

为了修复 var 的问题,ES6 引入了两个新关键字。

1. 块级作用域
letconst 声明的变量只在当前的 {} 块中有效。这完美解决了循环问题:

for(let i =0; i <3; i++){setTimeout(()=> console.log(i),100);}// 输出:0, 1, 2 ✅
浏览器控制台运行结果截图:var 的运行结果


浏览器控制台运行结果截图:let 的运行结果

2. 不存在变量提升
你必须遵循“先声明,后使用”的原则。

console.log(age);// 报错:ReferenceError: Cannot access 'age' before initializationlet age =18;
在这里插入图片描述

3. const 的特性

  • const 声明的是一个常量,一旦声明,必须立即初始化,且值不可重新赋值
  • 注意const 保证的是变量指向的内存地址不变。
    • 对于基本类型(数字、字符串),值就是内容,所以确实改不了。
    • 对于引用类型(对象、数组),虽然地址不能变,但内容可以改
constPI=3.14;// PI = 3.15; // 报错!TypeError: Assignment to constant variable.const user ={name:"Alice"}; user.name ="Bob";// ✅ 合法!对象内容修改了,但内存地址没变// user = {}; // ❌ 报错!试图改变内存地址

最佳实践

默认使用 const,当你发现后续需要修改这个变量时,再改回 let尽量不要使用 var

2.3 深入理解 TDZ(暂时性死区)

这是一个比较“硬核”但很重要的概念。

只要块级作用域内存在 letconst 命令,它所声明的变量就“绑定”这个区域,不再受外部影响。从块级开始到变量声明语句这行代码之间的区域,被称为暂时性死区

var tmp =123;// 全局变量if(true){// TDZ 开始// tmp = 'abc'; // 报错!ReferenceError// 这里虽然全局有 tmp,但在 let 声明前,不能访问 console.log(tmp);// 报错!let tmp;// TDZ 结束,tmp 绑定到此作用域 console.log(tmp);// undefined tmp =456; console.log(tmp);// 456}
在这里插入图片描述

TDZ 的存在让代码行为更加可预测,强制我们养成良好的变量声明习惯。

2.4 函数的全面升级

ES6 对函数做了大量增强,让写函数变得极其丝滑。

1. 参数默认值
再也不用写 var x = x || 1 这种 hack 语法了。

functionlogUser(name ="Guest", age =0){ console.log(`Name: ${name}, Age: ${age}`);}logUser();// Name: Guest, Age: 0logUser("Alice");// Name: Alice, Age: 0

2. 剩余参数
当你不确定传进来多少个参数时,用 ...args 把它们收成一个真正的数组。

// ES5 写法:arguments 是伪数组,很难用// ES6 写法:functionsum(...nums){return nums.reduce((total, num)=> total + num,0);} console.log(sum(1,2,3));// 6 console.log(sum(1,2,3,4,5));// 15

3. 箭头函数
这是 ES6 最流行的特性之一,语法极简。

// 普通constadd=function(a, b){return a + b;};// 箭头constadd=(a, b)=> a + b;// 只有一个参数时,括号可以省略constdouble=x=> x *2;

箭头函数的一个重要陷阱:没有自己的 this
箭头函数不会创建自己的 this 上下文,它会捕获其所在上下文的 this 值。这让它成为回调函数的最佳选择。

functionPerson(){this.age =0;// 普通 setInterval 里的 this 指向 window,导致 window.age++// 使用箭头函数,这里的 this 继承自 Person 实例setInterval(()=>{this.age++; console.log(this.age);},1000);}const p =newPerson();// 每秒输出 1, 2, 3...

注意:不要在对象的方法里使用箭头函数(除非你确定 this 指向外层),也不要用箭头函数作为构造函数(不能 new)。


3. 常见问题 (FAQ)

Q1:const 定义的数组或对象,真的完全改不了吗?
A:
不是。const 锁住的是内存地址。你依然可以修改对象的属性或使用数组方法(如 pushsplice)。如果想彻底冻结对象,可以使用 Object.freeze(obj)

Q2:什么情况下必须用 var
A:
几乎没有!除非你需要在旧版浏览器(如 IE10 及以下)运行代码,且没有 Babel 转译。在现代开发中,请忘掉 var

Q3:letconst 在全局作用域声明变量,会成为 window 的属性吗?
A:
不会。var 声明的全局变量会自动挂载到 window 上,而 let/const 不会,这避免了全局命名空间的污染。


4. 总结

本文我们完成了从“老旧 JS”到“现代 JS”的第一步跨越:

  1. 放弃 var:它没有块级作用域,存在变量提升,容易出 Bug。
  2. 拥抱 let/const:拥有块级作用域,存在 TDZ,更安全。默认用 const,需要改值用 let
  3. 箭头函数:语法简洁,解决了回调函数中 this 指向混乱的问题。

掌握这些特性,你的代码健壮性已经超越了 80% 的初学者。

下一篇预告:我们将学习让代码变得更优雅的“语法级”增强——解构赋值、模板字符串与展开运算符。它们能让你的代码量减少 30% 以上!


如果觉得本文对你有帮助,请点赞👍、收藏⭐、关注👀,三连支持一下!

有问题欢迎在评论区留言:你在写代码时习惯用 let 还是 const?

Read more

Tomcat安装及配置教程(保姆级)【最新史上最全版】

Tomcat安装及配置教程(保姆级)【最新史上最全版】

Tomcat安装教程 (以tomcat-9.0.62为例:) 1.下载安装包 可以从官网下载安装包: (1)从官网下载 输入网址进入官网 选择版本10,版本9,或者版本8,都可以,这里下载的版本9 不想去官网的直接百度网盘自提: 链接:https://pan.baidu.com/s/1_wWx48RVn_BSk3eXneAZYw?pwd=aijy 提取码:aijy 选择下载64-Bit Windows zip(Win64),根据电脑版本选择(目前大多数笔记本电脑都是64位滴) (2)选择解压路径 解压到电脑其中一个文件夹,记住解压路径 2.配置环境变量 (1)打开高级设置 电脑-属性-高级系统设置 (2)点击高级系统设置-环境变量-新建系统变量 (3)新建系统变量,变量名为CATALINA_HOME

By Ne0inhk
Java 大视界 -- 基于 Java+Storm 构建实时日志分析平台:从日志采集到告警可视化(440)

Java 大视界 -- 基于 Java+Storm 构建实时日志分析平台:从日志采集到告警可视化(440)

Java 大视界 -- 基于 Java+Storm 构建实时日志分析平台:从日志采集到告警可视化(440) * 引言: * 正文: * 一、实时日志分析平台的核心架构设计 * 1.1 架构分层与核心组件 * 1.2 组件选型的实战思考(10 余年经验沉淀,数据真实有出处) * 二、日志采集层:Flume 的高可用配置(生产级优化) * 2.1 Flume 的核心配置(抗住十万级 / 秒流量,注释完整) * 2.2 Flume 的高可用部署(避免单点故障,实战步骤清晰) * 2.2.1 多 Agent 冗余部署 * 2.2.2 Nginx

By Ne0inhk
用 Java 实现控制台版图书管理系统:从需求到代码的完整实践

用 Java 实现控制台版图书管理系统:从需求到代码的完整实践

我不是广告 个人主页-爱因斯晨 文章专栏-JAVA学习 好久不见~最近变了很多,也在忙。也有点儿小体会吧,最近遇到了很多事儿,我也想了很多。我个人的想法还是:不能给自己的以后留下任何污点,因为路还很长,我这才刚开始。要坚守自己的底线吧!“苟非吾之所有,虽一毫而莫取” 最后,衷心祝大家,身心健康,注意好身体! > 不知道大家喜欢听歌嘛?最近发现一个可以白嫖会员的东西,苹果音乐可以白嫖会员(新用户两个月,老用户一个月),苹果安卓都能用,领取之后记得关闭自动续费哦~曲库还是很多的,大家可以点击链接领取。领取链接绝对免费!绝对白嫖! 作为一名 Java 开发者,我们常常忙于框架和中间件的使用,却容易忽略基础语法的实战价值。今天,我将带大家从零开始实现一个控制台版图书管理系统,这个项目虽然简单,却涵盖了 Java 核心基础的大部分知识点,非常适合初学者巩固基础,也能让资深开发者重温 Java 设计的初心。 项目需求分析 在开始编码之前,我们需要明确这个图书管理系统应该具备哪些核心功能。

By Ne0inhk