从零学 JavaScript:彻底搞懂 var、let、const(下篇)——深入理解常量、作用域与冻结对象

54 阅读2分钟

上篇回顾:我们已经知道

  • var 有变量提升,不支持块级作用域,已过时
  • letconst 解决了这些问题,是现代 JS 的首选
  • const 声明的变量不能重新赋值
  • letconst 有“暂时性死区”,不能在声明前访问

但还有一个关键问题:为什么 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 是浅冻结,对象内部的嵌套对象仍可修改。如需深冻结,需递归实现。


三、块级作用域:letconst 的核心优势

{
  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);
}

vari 是函数作用域,循环结束后 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,无法调用。


五、总结:一张表看懂三者区别

特性varletconst
变量提升✅(值为 undefined❌(暂时性死区)❌(暂时性死区)
块级作用域
可重新赋值
可重复声明✅(同一作用域)

最后:用现代 JavaScript 写更安全的代码

var 是历史的产物,letconst 是未来的标准。
掌握它们的区别,不仅能避免 bug,更能写出更清晰、更可维护的代码。

记住:

  • 优先使用 const
  • 需要重新赋值时用 let
  • 彻底告别 var