JavaScript 变量声明:你真的懂var、let 和 const 吗

核心区别总结

作用域

函数作用域

块级作用域

块级作用域

变量提升

提升并初始化为undefined

提升但不初始化(TDZ)

提升但不初始化(TDZ)

重新赋值

允许

允许

不允许

重复声明

允许

不允许

不允许

循环中的行为

共享同一个变量

每次迭代创建新变量

不能用于传统for循环

作用域详解

1. 函数作用域 (var)

function test() {
  var x = 1;
  if (true) {
    var x = 2; // 同一个变量
  }
  console.log(x); // 2
}

2. 块级作用域 (let/const)

function test() {
  let x = 1;
  if (true) {
    let x = 2; // 不同的变量
  }
  console.log(x); // 1
}

暂时性死区 (TDZ)

console.log(a); // undefined (var)
var a = 1;

console.log(b); // ReferenceError (let/const在TDZ)
let b = 2;

循环中的表现

1. 传统for循环

// var - 有问题
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 3,3,3
}

// let - 正确
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 0,1,2
}

// const - 报错
for (const i = 0; i < 3; i++) {} // TypeError

2. for...of/in循环

// 数组遍历
const arr = [1, 2, 3];
for (const item of arr) {
  console.log(item); // 1,2,3
}

// 对象遍历
const obj = {a:1, b:2};
for (const key in obj) {
  console.log(key); // 'a','b'
}

最佳实践

  1. 默认使用 const用于所有不需要重新赋值的变量更安全,意图更明确
  2. 需要重新赋值时使用 let循环计数器需要后续修改的变量
  3. 永远不要使用 var只在维护旧代码时使用新项目完全避免
  4. 异步代码特别注意

常见问题解答

Q: 为什么const声明的对象可以修改属性?A: const只保证变量绑定不变,对象内容可以修改。若要完全不可变,使用Object.freeze()

Q: 什么时候必须用let?A: 1) 需要重新赋值的变量 2) 传统for循环的计数器

Q: TDZ是什么?A: 暂时性死区(Temporal Dead Zone),指从进入作用域到变量声明之间的区域,访问会报错

记忆口诀

  • "var会提升,let会报错"
  • "const不变,let可变"
  • "循环异步,let是必须"
  • "遍历数据,const优先"

掌握这些区别和最佳实践,可以避免JavaScript中大多数变量相关的问题!

var let 和const

var 函数作用域

函数作用域

  • 由函数创建的作用域
  • 使用 var 声明的变量具有函数作用域
  • 在整个函数内部都可用,无论声明在函数的哪个位置

块级作用域

  • 由代码块({})创建的作用域
  • 使用 letconst 声明的变量具有块级作用域
  • 只在声明它的代码块内可用

let块级作用域 定义时候可以不初始化

const 块级作用域 定义时候要初始化

三种变量提升的场景
function example() {
  console.log(x); // 输出 undefined 而不是报错 变量提升但是不赋值
  var x = 5;
}


{
  console.log(y); // 暂时性死区 报错ReferenceError: Cannot access 'y' before initialization
  let y = 10;
}

{
  console.log(y); // 暂时性死区 报错ReferenceError: Cannot access 'y' before initialization
  const y = 10;
}

三个错误使用const的场景
for (var i = 0; i < 3; i++) {
 setTimeout(() => console.log(i), 100); 
}
// 输出:3, 3, 3 异步共享变量

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

for (const i = 0; i < 3; i++) {  // TypeError: Assignment to constant variable
 setTimeout(() => console.log(i), 100);
}
// 直接报错!

for (const i of [0, 1, 2]) {
 setTimeout(() => console.log(i), 100); 
}
// 输出:0, 1, 2


<button id="btn0">按钮0</button>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>

<script>
// 用 var 的错误写法
for (var i = 0; i < 3; i++) {//一开始就运行完了 i = 3
 document.getElementById(`btn${i}`).addEventListener('click', () => {
   console.log(`点击了按钮${i}`); //点击按钮时候才输出
 });
}
// 无论点击哪个按钮,都会输出"点击了按钮3"
</script>





使用 const 的场景:当且仅当循环变量在迭代过程中不会被重新赋值

1. for...of 循环中使用 const
javascript
const arr = [10, 20, 30];
for (const item of arr) {
  console.log(item); // 输出 10, 20, 30
}
为什么可以用 const?
for...of 循环在每次迭代时都会创建一个新的 item 绑定

你不会(也不能)在循环体内修改 item 的值

每次迭代的 item 都是一个全新的常量

错误尝试:
javascript
for (const item of [1,2,3]) {
  item = item * 2; // TypeError: Assignment to constant variable
}
2. for...in 循环中使用 const
javascript
const obj = {a: 1, b: 2};
for (const key in obj) {
  console.log(key); // 输出 'a', 'b'
}
为什么可以用 const?
每次迭代都会创建一个新的 key 绑定

你通常不需要修改遍历得到的键名

如果你尝试修改会报错

全部评论

相关推荐

不愿透露姓名的神秘牛友
07-01 11:27
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务