var_let_const

JavaScript ES6:var、let、const 学习笔记

参考目录:D:\learn329\wa\hl_ai\fe\js\es6\var_let_const

本文结合 1.js~4.js 的实际运行结果整理,并对容易混淆的概念作了校正。

1. 先建立整体认识

JavaScript 的变量不是"脱离位置而存在"的:变量声明在哪个作用域,就只能在相应的范围内访问。ES6(2015)新增 let 与 const,主要是为了弥补 var 在块级作用域、重复声明和变量提升方面容易造成的错误。

现在写业务代码时,一个实用的默认规则是:

js 复制代码
const apiUrl = '/api/users'; // 默认先用 const

let total = 0;               // 以后确实要重新赋值时,才用 let
total += 1;

// var 只在维护旧代码或需要理解历史代码时使用

注意:JavaScript 是动态类型语言,let 声明的变量可以被重新赋成另一种类型,但这通常会降低可读性,应尽量避免。

js 复制代码
let points = 50;
points = '52'; // 语法允许,但不推荐:同一个变量的含义变得不稳定

2. 作用域:变量"在哪里可见"

可以把作用域理解为变量的可访问范围。常见的作用域有:

类型 典型位置 例子
全局作用域 最外层 const siteName = 'Demo';
函数作用域 函数体内部 function f() { var x = 1; }
块级作用域 {} 内部,如 if、for if (ok) { let x = 1; }

访问变量时,JavaScript 会先在当前作用域 找;找不到就逐层向外层作用域查找;直到全局作用域仍找不到,便抛出 ReferenceError。这个过程常被称为词法作用域下的"向外查找"。

1.js:函数作用域和块级作用域

js 复制代码
var height = 100;

function setWidth() {
  var width = 100;
  console.log(width, height);
}

setWidth(); // 100 100

函数内部能读取自己的 width,也能读取外层的 height;但函数外不能直接读取 width。

再看条件语句:

js 复制代码
var age = 100;

if (age > 12) {
  var dog = age * 7;
  let x = 111;
  console.log(dog); // 700
  dog++;
}

console.log(dog); // 701
console.log(x);   // ReferenceError: x is not defined

这里正好体现二者差异:

  • var dog 不具有块级作用域,因此 if 外仍然能访问到它。
  • let x 只属于这对 {},离开代码块后就不能访问。

因此,在 if、for、while、try...catch 等代码块中声明临时变量,优先使用 let 或 const。

3. var、let、const 对比

特性 var let const
作用域 函数作用域(没有块级作用域) 块级作用域 块级作用域
同一作用域能否重复声明 可以,容易覆盖 不可以 不可以
声明后能否重新赋值 可以 可以 不可以重新绑定
是否必须声明时赋值 不必须 不必须 必须
初始化前读取 得到 undefined(典型情况) 抛出 ReferenceError 抛出 ReferenceError

为什么少用 var

var 的"函数作用域 + 可重复声明 + 提升后值为 undefined"会让变量的真实状态不够直观。例如:

js 复制代码
if (true) {
  var message = 'hello';
}
console.log(message); // hello,很多初学者会误以为这里应该访问不到

改为 let 后,变量被限制在块内,代码边界更清晰:

js 复制代码
if (true) {
  const message = 'hello';
}
console.log(message); // ReferenceError

4. for 与 setTimeout:为什么 let 能保存每轮的 i

2.js 使用了:

js 复制代码
for (let i = 0; i < 10; i++) {
  console.log(i);
  setTimeout(function () {
    console.log(`This number is ${i}`);
  });
}

同步部分先打印 0 到 9;定时器回调稍后执行时,也依次打印 This number is 0 到 This number is 9。

原因是:在 for (let i = ...) 中,let 会让每一次循环迭代拥有独立的 i 绑定;每个异步回调闭包捕获的是当轮的那个 i。

若换成 var:

js 复制代码
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  });
}

循环结束时唯一的 i 已经是 3,所以通常会输出三次 3。旧代码常用立即执行函数(IIFE)解决此问题;ES6 中直接用 let 更清楚。

5. const:不能改"绑定",不等于对象完全不可变

基本类型

3.js 中:

js 复制代码
const key = 'abc123';
// key = 'ABC123'; // TypeError: Assignment to constant variable.

const 在声明时必须初始化,之后不能把同一个变量重新指向其他值。

对象和数组

const 限制的是变量保存的引用/绑定,不是对象内部的每一个属性。因此下面代码合法:

js 复制代码
const person = { name: '天启', age: 18 };
person.age++;
console.log(person); // { name: '天启', age: 19 }

但重新给 person 赋值不合法:

js 复制代码
person = '111'; // TypeError: Assignment to constant variable.

同理,const list = [] 后可以 list.push(...),但不能执行 list = []。如果业务确实需要防止对象属性被修改,可考虑 Object.freeze();不过它默认只冻结第一层,深层对象仍需要额外处理。

6. 变量提升与暂时性死区(TDZ)

4.js:

js 复制代码
console.log(pizza);
let pizza = 'Deep Dish';

运行会报:

text 复制代码
ReferenceError: Cannot access 'pizza' before initialization

这是 let 的暂时性死区(Temporal Dead Zone,TDZ):从进入当前作用域开始,到执行声明并初始化的这一行之前,变量已经处于作用域中,却不能被读取或使用。

常见简化说法是"let/const 没有变量提升",但更准确的表达是:

  • var、let、const 的声明都会在作用域建立时被处理;
  • var 会被初始化为 undefined,所以声明语句之前读取通常不会立即报错;
  • let 和 const 在声明语句执行前处于 TDZ,访问会报错;
  • let/const 不是"绝对不存在提升",而是不能像 var 那样提前使用。

对比:

js 复制代码
console.log(a); // undefined
var a = 1;

console.log(b); // ReferenceError
let b = 1;

最稳妥的编码习惯是不依赖任何形式的提前访问:先声明并初始化,再使用变量。

7. 关于变量生命周期的一点补充

函数或代码块结束后,其局部变量通常不再能从外部访问;如果没有任何引用指向相关数据,JavaScript 引擎会在合适的时机进行垃圾回收。这里有两个边界:

  • 垃圾回收不是"代码块一结束就立刻发生",具体时机由引擎决定;
  • 如果闭包仍引用局部变量,那么函数返回后该变量仍可能继续存活。
js 复制代码
function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

const counter = makeCounter();
counter(); // 1,count 仍被内部函数引用

8. 实战选择清单

  1. 默认使用 const:变量本身不需要重新赋值时,用它表达"绑定不变"的意图。
  2. 确需重新赋值时使用 let:如计数器、累加器、会更新的状态。
  3. 新代码避免 var:除非是在维护旧项目,或需要与其历史行为保持一致。
  4. 不要在声明前访问变量:它会导致 var 的 undefined 问题或 let/const 的 TDZ 错误。
  5. 不要把 const 当深度不可变:对象、数组内部仍可改变;有不可变需求时使用冻结、复制或状态管理策略。
  6. 在循环异步回调中优先用 let:避免多个回调共享同一个 var i。

9. 小练习

判断下面每段代码的结果,并自己运行验证:

js 复制代码
// A
if (true) {
  var a = 1;
  let b = 2;
}
console.log(a);
console.log(b);

// B
const user = { score: 0 };
user.score += 1;
// user = { score: 1 };

// C
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i));
}

答案:A 中先输出 1,随后因 b 不在块外作用域而报 ReferenceError;B 中修改属性合法、重新赋值不合法;C 中异步输出 0、1、2。

相关推荐
Maxkim1 小时前
私钥就是数字 4:扒一扒这场把一家 CA 直接送走的事故
javascript·架构
u0111026752 小时前
Canvas 导出 PNG 时 quality 参数为什么不起作用
前端·javascript·vue.js
2601_949847752 小时前
jose 跨端三方库仓颉适配全流程
前端·javascript·flutter
CappuccinoRose2 小时前
滚动交互体系
前端·javascript·交互·html5·1024程序员节
开开心心就好2 小时前
电脑小说阅读器,支持朗读和自定义皮肤
服务器·开发语言·javascript·算法·pdf·c#·音视频
2601_949970694 小时前
msgpackr 跨端三方库仓颉适配全流程
前端·javascript·flutter
数智奔流5 小时前
MiniMax的低价攻势,撞上“背题”疑云
前端·javascript·算法
某人辛木5 小时前
Web前端框架应用(三)路由
前端·javascript·前端框架
小大宇16 小时前
Vue知识点6-computed计算属性
前端·javascript·vue.js