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 22:23
已编辑
字节跳动_实习生(实习员工)
1.简单的自我介绍2.八股和项目C++&nbsp;相关怎么获取一个变量占用的内存的大小?如果你对一个指针用&nbsp;sizeof,它的返回值是什么?如果是对普通的指针使用&nbsp;sizeof,它的大小是多少?方法的签名包含哪些部分?用过&nbsp;C++&nbsp;的模板编程吗?在模板编程里面,为什么会要求你把模板的声明和实现都放在头文件里面?了解这个模板是怎么实现的吗?它的原理是什么?操作系统与多线程/多进程项目中的互斥锁是可重入的吗?了解过可重入锁和不可重入锁吗?请介绍一下自旋锁。自旋锁会有额外的性能损耗吗?你了解内核态和用户态的情况吗?内核态和用户态,是怎么实现隔离的呢?进行系统调用的时候会发生什么?子进程它怎么和主进程去交互呢?除了管道,还有其他进程间通信的方式吗?进程和线程有什么区别?进程之间的内存是共享的吗?有没有接触过线程之间不共享内存的语言?数据库与网络了解&nbsp;Protobuf&nbsp;(PB)&nbsp;吗?为什么(在你的项目中)使用&nbsp;SQLite&nbsp;而不是&nbsp;MySQL?你的项目中有用索引吗?请介绍一下数据库索引。3.手撕:有序数组查找范围(两次二分查找)4.反问面试流程大概要多久?什么时候能知道结果?面试官年轻了点,人还是很友好的,不懂的也不会一直追问,对项目的提问还是有一定深度的,答上来了大概70-80%。
查看24道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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