重生之我在 Vibe Coding 时代当程序员:第十课,var、let、const:ES6变量声明的革命
上节课用 Python 调了大模型,这节回到 JavaScript 基础——ES6 的变量声明。老师几句话就给我们展现了 JavaScript 的一些知识点 “JavaScript 蹭了一波 java 的热度,弱类型动态语言,早期设计用来给网页添加交互(幻灯片),DOM 编程”。这节课不是讲“AI 怎么帮你写代码”,而是“你怎么用现代 JavaScript 写出更健壮的代码”。
从 ES5 到 ES6:一次“补课式”的升级
JS 是以 ECMAScript 为语言标准的语言。ES6 是 JS 的新版本,ES5 是旧版,ES6+ 是现在主流。2015 年出版 ES6,向企业级大型项目开发发展。老师调侃说:“JS 是一个 KPI 项目,一周就开发出来。” 这解释了为什么早期设计有那么多瑕疵——比如没有块级作用域,只能用 var 声明变量。
早期 JS 使用 var 声明变量,没有常量声明,不支持块级作用域(只支持函数作用域)。当时只能使用代码规范约束(比如变量名字全大写表示常量)。JS 早期设计是浏览器的副产品——微软成功的原因,用户买电脑、用户装 IE、公司投广告——因而微软想更进一步,想要开发一款浏览器的编程语言,能够修改浏览器上的布局、交互等等。JS 没有经过深思熟虑,有一些瑕疵。
ES6 的 let 和 const 就是来“补课”的:let 负责变量,const 负责常量,两者都支持块级作用域。
声明变量并赋值:var、let、const 的区别
笔记里明确列出了三种声明方式:
- var:ES5 使用的,现在不使用
- let:ES6+,现在已代替 var,用于声明变量
- const:ES6+,用于声明常量
关键区别在于作用域和重新赋值的能力。let 声明的变量可以重新赋值,const 声明的常量不能重新赋值(但复杂类型的属性可以修改)。const 声明和赋值要一起,let 声明和赋值可以分开。
// const 声明和赋值要一起,let 声明和赋值可以分开
const item = 1;
let a;
const key = 'abc123';
// let 定义的值,不止值能改变,连类型都能改变,但不要这样干,不好维护
作用域:变量查找的规则
作用域是变量生效的范围。笔记里详细解释了作用域的嵌套关系:
- 冒泡查找:从内向外一步步查找,里面可以找到外面的元素,外面无法使用里面的元素
- 全局作用域
- 函数局部作用域
- 内嵌函数局部作用域
- 块级作用域
{ }中的代码块
变量属于作用域,变量声明时 JS 作为弱类型语言,类型由值决定。
// 局部作用域 global scope 全局作用域
function setWidth(width) {
//函数局部作用域
var width = 100;
console.log(width);
}
setWidth();
// console.log(width);
var age = 18;
if (age > 12) {
// es6 常量定义方式 const 常量不能重新赋值
const dog = age * 7;
console.log(dog);
// 常量不能重新赋值
dog++;
// console.log(dog);
}
console.log(dog);
查找变量的规则(静态作用域与闭包...):
- 先在当前作用域查找
- 找到了,返回
- 没找到,向外层作用域查找
- 当在全局作用域都没找到,停下来并返回报错
//全局作用域
{
// 代码块 块级作用域
// 声明了变量,属于当前块级作用域
const name = '张三';
console.log(name);
}
// console.log(name);
当代码块/函数运行之后,垃圾(内存)会自动回收。从内存角度理解变量声明:
- 在内存中申请了一块区域
- 用完之后,会销毁函数,回收函数占据的内存
const 的特殊性:值不变,但对象属性可变
const **保护的是“变量绑定不变”,不是“值完全不可变”。**对于简单数据类型,不能重新赋值;对于复杂数据类型(对象),不能重新对对象赋值,但可以修改对象中的属性值。
// 当 const 定义简单数据类型时,不能重新赋值
// 当 const 定义复杂数据类型时,可以重新赋值,但是不能重新定义对象,只能重新定义对象的属性
// 所以 const 不能赋值不论是对于简单数据类型还是对于复杂数据类型,其实是不能修改他对应的内存地址
const obj = {
name: '张三',
age: 18
}
obj.name = '李四';
console.log(obj);
常量一开始必须赋值(如果都没有值,如何不能赋值呢),不能重新赋值。常量是 constant variable,不可变变量。
常见错误:Assignment to constant variable
尝试给一个用 const 声明的变量重新赋值,会抛出 Assignment to constant variable 错误。
const MAX = 100;
MAX = 200; // Assignment to constant variable
这个错误信息明确告诉你:你试图修改一个常量的绑定。const 保护的是“变量绑定不变”,不是“值完全不可变”。在对象之中,对象的地址不能变(不能给对象重新赋值),但是可以修改对象中的属性值。
经典问题:for 循环与 setTimeout 的闭包陷阱
这是面试常考题,也是理解块级作用域的最佳示例。
// 循环变量i 是全局变量,循环结束后,i 会存在全局作用域中
// 所以使用 var 异步操作会全部使用最后的 i 也就是10
// 解决方法:使用 let 定义循环变量i,循环结束后,i 会存在当前块级作用域中,
// 不会存在全局作用域中,代码会执行10次,所以一共有10个块级作用域,
// 每个块级作用域都有自己的i
for (let i = 0; i < 10; i++) {
// 同步代码 尽快执行完
console.log(i);
// 异步代码 1秒(1000ms)后执行完
setTimeout(() => {
console.log(`this is ${i}`);
}, 1000);
}
var 不支持块级作用域,只有一个 i,循环先执行完,i 已经变成 10,然后 setTimeout 的回调才执行。let 支持块级作用域,执行 10 次,所以有 10 个块级作用域,每次循环迭代都创建一个独立的块级作用域,每个作用域有自己的 i。
变量提升(Hoisting):JS 的“糟粕”
代码执行分为两个阶段:
- 编译阶段:变量声明,作用域确定,检测代码语法,准备好执行上下文(变量环境)
- 执行阶段:在执行阶段之间就有声明
var 声明的全局变量在编译阶段会被赋值为 undefined,因为赋值也是执行阶段才会执行的。这是一个缺点、糟粕,不好的东西——与代码顺序以及直觉不符合。避免变量提升是现代 JS 的最佳实践。
// 代码执行顺序
// 编译阶段 compile 变量声明,作用域确定 检测代码语法 准备好执行上下文(变量环境)
// 执行阶段 在执行阶段之间就有声明
console.log(pizza);
let pizza = 'Deep Dish';
let 不支持变量提升,上面的代码会报错:ReferenceError: Cannot access 'pizza' before initialization。
变量被引用之前要先被声明
变量被引用之前要先被声明,否则会报错:ReferenceError : XXX is not defined——你在代码里使用了变量/函数 XXX,但在当前作用域中并没有定义它,所以运行时报错。
// 正确:先声明,再使用
let name = "Alice";
console.log(name); // "Alice"
// 错误:未声明就使用(会抛出 ReferenceError)
console.log(age); // ReferenceError: age is not defined
注意:var 有变量提升(hoisting)的特性,声明会被提升到作用域顶部,但赋值不会。
console.log(x); // undefined(不报错,因为声明被提升了)
var x = 10;
赋值与修改的本质
关于“赋值与修改本质相同”,这句话的意思是:无论是第一次给变量赋值,还是后续修改变量的值,底层操作是一样的——都是将一个新值绑定到变量名上。
let count = 0; // 赋值(初始化)
count = 1; // 修改
count = 2; // 再次修改
这三行做的事情本质上没有区别,都是让 count 这个名字指向一个新的值。变量本身只是一个“标签”,赋值就是把标签贴到某个值上,修改就是把标签重新贴到另一个值上。
这一点在理解引用类型时尤为重要:
let obj = { a: 1 }; // obj 指向一个对象
obj = { a: 2 }; // obj 现在指向另一个对象(原对象不变)
obj.a = 3; // 修改的是对象内部的属性,obj 本身的指向没变
前两行是对变量 obj 本身的重新赋值,第三行是对对象属性的修改,两者是不同层面的操作。
总结:从“一周 KPI”到“企业级标准”
ES6 的 let 和 const 不只是语法糖,而是对 JS 早期设计缺陷的系统性修复。块级作用域解决了变量污染问题,常量声明让代码意图更清晰,变量提升的避免让代码执行顺序更符合直觉。这些改进让 JavaScript 从“浏览器脚本语言”真正走向了“企业级大型项目开发语言”。
作为程序开发者,理解这些基础概念比会用 AI 写代码更重要。AI 可以帮你生成 let 和 const,但为什么要用它们、什么时候用哪个,需要你自己判断。这节课让我明白:编程语言的设计历史,就是一部不断“补课”的历史。