上篇回顾:我们已经知道
var有变量提升,不支持块级作用域,已过时let和const解决了这些问题,是现代 JS 的首选const声明的变量不能重新赋值let和const有“暂时性死区”,不能在声明前访问
但还有一个关键问题:为什么 const 声明的对象,属性还能修改?
一、const 的真相:只冻结“引用”,不冻结“内容”
const person = {
name: 'wcb',
age: 18
};
person.age = 21; // ✅ 可以修改
console.log(person); // { name: 'wcb', age: 21 }
person = {}; // ❌ 报错!Assignment to constant variable.
原因:
const 保证的是变量指向的内存地址不变,而不是对象内部的值不变。
person.age = 21:修改的是对象内部的属性,地址没变person = {}:试图改变person的引用地址,被禁止
类比:你把房子的钥匙(地址)交给别人,他不能换一把新钥匙,但可以在房子里装修。
二、如何真正“冻结”一个对象?Object.freeze()
如果你希望一个对象完全不可变,可以使用:
const wes = Object.freeze({
name: 'wcb',
age: 18
});
wes.age = 22; // ❌ 在严格模式下会报错,非严格模式静默失败
console.log(wes); // { name: 'wcb', age: 18 }
Object.freeze() 会:
- 禁止添加/删除属性
- 禁止修改现有属性的值
- 禁止修改属性描述符
⚠️ 注意:
freeze是浅冻结,对象内部的嵌套对象仍可修改。如需深冻结,需递归实现。
三、块级作用域:let 和 const 的核心优势
{
var a = 18;
let b = 188;
}
console.log(a); // 18 —— var 泄露到全局
console.log(b); // ❌ 报错!b is not defined
为什么块级作用域如此重要?
在 for 循环中:
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 3 3 3,而不是 0 1 2
}, 100);
}
var 的 i 是函数作用域,循环结束后 i = 3,所有 setTimeout 都引用同一个 i。
使用 let:
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 0 1 2
}, 100);
}
let 为每次循环创建一个新的块级作用域,i 是独立的。
四、函数提升 vs 变量提升
JavaScript 中还有一个特殊现象:函数提升。
setWidth(); // ✅ 正常执行
function setWidth() {
console.log('100');
}
函数声明不仅会提升声明,还会连同函数体一起提升。
但函数表达式不同:
setHeight(); // ❌ 报错!setHeight is not a function
var setHeight = function() {
console.log('188');
};
因为 var 只提升声明,setHeight = undefined,无法调用。
五、总结:一张表看懂三者区别
| 特性 | var | let | const |
|---|---|---|---|
| 变量提升 | ✅(值为 undefined) | ❌(暂时性死区) | ❌(暂时性死区) |
| 块级作用域 | ❌ | ✅ | ✅ |
| 可重新赋值 | ✅ | ✅ | ❌ |
| 可重复声明 | ✅(同一作用域) | ❌ | ❌ |
最后:用现代 JavaScript 写更安全的代码
var 是历史的产物,let 和 const 是未来的标准。
掌握它们的区别,不仅能避免 bug,更能写出更清晰、更可维护的代码。
记住:
- 优先使用
const- 需要重新赋值时用
let- 彻底告别
var