news 2026/4/3 1:26:12

ES6 let与const变量声明:完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ES6 let与const变量声明:完整指南

varconst:现代 JavaScript 变量声明的进化之路

你有没有在调试时遇到过这样的困惑——明明还没声明一个变量,却能访问到它,值还是undefined?或者在一个循环里绑定了多个事件回调,结果它们全都输出同一个值?

这些“诡异”的行为,根源往往就藏在 JavaScript 的变量声明机制中。而这一切,在 ES6 引入letconst之后,彻底改变了。

今天我们就来深入聊聊这两个看似简单、实则影响深远的关键字,看看它们是如何重塑我们写 JavaScript 的方式的。


为什么我们需要新的变量声明?

在 ES6 之前,JavaScript 只有一个正式的变量声明方式:var

console.log(name); // undefined var name = "Alice";

这段代码不会报错,但结果可能让你一头雾水。变量被“提升”了——即声明被移到作用域顶部,但赋值仍保留在原地。于是你就得到了一个“存在但未初始化”的状态。

更麻烦的是作用域问题:

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

所有异步回调共享的是同一个i,因为var是函数级作用域,而不是按{}块隔离。这在闭包场景下极易引发 bug。

这些问题促使 TC39 推出了新的解决方案:letconst


let:让变量回归逻辑直觉

它解决了什么?

  • 块级作用域let把变量绑定到最近的一对{}中。
  • 暂时性死区(TDZ):不再允许先使用后声明。
  • 禁止重复定义:避免意外覆盖。

来看个例子:

{ let message = "Hello"; } console.log(message); // ❌ ReferenceError

这个错误反而是好事——它暴露了逻辑错误,而不是默默给你一个undefined

再看经典的循环问题:

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

神奇吗?其实不。每次迭代都会创建一个新的词法环境,每个i都是独立绑定的。这是引擎自动帮你做了闭包隔离,无需手动 IIFE。

TDZ 是什么?

console.log(x); // ❌ Cannot access 'x' before initialization let x = 10;

虽然语法上说“let也会提升”,但它进入了一个叫“暂时性死区”的区域——从作用域开始到实际声明语句之间,该变量不可用。这种设计强制开发者遵循“先声明后使用”的良好习惯。

还有哪些细节值得注意?

  • 在全局作用域中用let声明的变量,不会挂在window上(浏览器),避免污染全局对象。
  • 同一作用域内不能重复声明:
let user = "Tom"; let user = "Jerry"; // SyntaxError

这一点比var严格得多,也安全得多。


const:不只是“常量”,更是编程思维的转变

很多人以为const是定义“不可变的值”,但实际上它定义的是“不可变的绑定”。

什么意思?

const PI = 3.14159; PI = 3.14; // ❌ TypeError: Assignment to constant variable.

基本类型没问题,重新赋值直接抛错。

但对象呢?

const user = { name: "Bob" }; user.name = "Alice"; // ✅ 允许! user = {}; // ❌ 不允许!

看到了吗?const锁住的是“user指向这个对象”这件事,而不是对象本身的内容。你可以修改属性,但不能把user指向另一个新对象。

这说明了一点:const提供的是引用不变性,不是深冻结

如果你真的需要完全不可变的对象,得配合Object.freeze()

const config = Object.freeze({ apiBase: '/api', timeout: 5000, headers: { 'Content-Type': 'application/json' } }); config.timeout = 6000; // ❌ 无声失败(严格模式下报错) config.headers['X-Token'] = 'xxx'; // ⚠️ 注意:嵌套对象仍可变!

小贴士:Object.freeze()是浅冻结。如果要深层冻结,需要递归处理或使用库如deep-freeze


let/constvsvar:一场作用域革命

特性varletconst
作用域函数级块级块级
提升行为提升并初始化为undefined提升但处于 TDZ提升但处于 TDZ
可重复声明允许禁止禁止
绑定全局对象是(如window.x
是否必须初始化
是否允许重新赋值

关键差异总结一句话:
var是宽松且容易出错的;letconst是严谨且可预测的。


实战中的最佳实践

1. 默认用const,只在必要时降级为let

这是一个被广泛采纳的原则。问问自己:这个变量后面还会被重新赋值吗?

const apiUrl = 'https://api.example.com'; const users = await fetchUsers(); let retries = 0; while (retries < 3) { try { return await fetchData(); } catch { retries++; // 必须改变值 → 用 let } }

这样做的好处:
- 明确表达意图:“我本不想改它”
- 减少意外重写的风险
- 更利于静态分析和性能优化(V8 引擎会对const做更多假设)

2. 在模块配置中大量使用const

现代前端项目高度依赖配置驱动:

const API_VERSION = 'v2'; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const SUPPORTED_FORMATS = ['jpg', 'png', 'gif']; /** * 创建请求实例 */ const request = axios.create({ baseURL: `${API_ROOT}/${API_VERSION}`, timeout: REQUEST_TIMEOUT, });

这些值一旦设定就不应变动,用const正合适。

3. 函数表达式优先用const

const handleClick = () => { console.log('Button clicked'); }; const validateForm = function() { // ... };

防止函数引用被意外覆盖。比起function声明,这种方式更能体现“函数即值”的理念。

4. 循环中优先使用let

即使你不打算修改循环变量,也推荐用let而非var

for (let i = 0; i < items.length; i++) { const item = items[i]; setTimeout(() => console.log(item), 100); }

不仅能避免闭包陷阱,还能让i的生命周期控制在循环体内。


工程化建议:用工具守住底线

光靠自觉不够,要用 ESLint 把规范固化下来。

推荐启用以下规则:

{ "rules": { "no-var": "error", // 禁止使用 var "prefer-const": "warn", // 如果从未重新赋值,提示改用 const "const-spacing": ["error", "both"] // const 前后空格一致性 } }

加上"env": { "es6": true }或使用eslint:recommended,就能在开发阶段提前发现问题。

配合 Prettier 和编辑器集成,团队协作效率会大幅提升。


写在最后:不仅仅是语法糖

letconst看似只是多了两个关键字,实则是 JavaScript 成熟化的标志之一。

它们推动我们从“随意声明”走向“精确控制”,从“动态混乱”迈向“结构清晰”。更重要的是,它们引导我们形成一种新的编程哲学:

尽可能减少可变性,明确变量生命周期,增强代码自文档能力。

当你开始习惯写const x = ...并只在真正需要时才改为let,你就已经踏上了写出更健壮、更易维护代码的道路。

而这,正是现代 JavaScript 开发的第一课。

你在项目中已经全面转向let/const了吗?有没有遇到过因为var导致的坑?欢迎在评论区分享你的故事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/2 20:50:34

负载均衡部署设想:应对高并发识别请求

负载均衡部署设想&#xff1a;应对高并发识别请求 在智能会议系统日益普及的今天&#xff0c;一场线上跨国会议可能同时产生数十路音频流&#xff0c;每一路都需要实时转写成文字用于字幕、纪要和合规存档。这种场景下&#xff0c;传统的单机语音识别服务往往不堪重负——刚启动…

作者头像 李华
网站建设 2026/3/13 16:09:34

API接口文档生成:Swagger集成方案探讨

API接口文档生成&#xff1a;Swagger集成方案探讨 在当今快速迭代的软件开发环境中&#xff0c;一个常见的场景是&#xff1a;前端工程师正准备对接一个新的语音识别功能&#xff0c;却发现后端提供的接口文档还是两周前的版本&#xff0c;字段命名不一致、参数缺失、响应示例过…

作者头像 李华
网站建设 2026/3/21 13:16:01

构建GLM-TTS性能基准测试套件:统一评估标准

构建GLM-TTS性能基准测试套件&#xff1a;统一评估标准 在智能语音产品快速迭代的今天&#xff0c;一个看似流畅的语音助手背后&#xff0c;可能隐藏着数十种不同的合成策略——有的音色自然但延迟高&#xff0c;有的响应飞快却发音生硬。尤其当大语言模型开始深度介入语音生成…

作者头像 李华
网站建设 2026/4/2 10:26:55

GLM-TTS支持MP3格式输入吗?常见音频格式兼容性说明

GLM-TTS 支持 MP3 格式输入吗&#xff1f;常见音频格式兼容性说明 在语音合成技术日益普及的今天&#xff0c;越来越多用户希望用自己的声音“复活”一段文字——无论是为有声书配音、打造专属语音助手&#xff0c;还是保存亲人的声音记忆。而实现这一切的关键&#xff0c;往往…

作者头像 李华
网站建设 2026/3/28 3:01:21

IFTTT小程序:个人生活场景下的智能化语音提醒

IFTTT小程序&#xff1a;个人生活场景下的智能化语音提醒 在智能设备日益渗透日常生活的今天&#xff0c;我们早已习惯了手机闹钟、日程提醒和智能家居的自动响应。但你是否曾想过——如果清晨响起的不是冰冷的“滴——请起床”&#xff0c;而是爱人轻声说“宝贝&#xff0c;该…

作者头像 李华
网站建设 2026/4/2 13:27:51

【JavaScript】forEach 是按数组顺序执行吗?

简单直接的回答是&#xff1a;是的&#xff0c;在同步代码中&#xff0c;forEach 是严格按照数组索引顺序&#xff08;从 0 到 length-1&#xff09;执行的。 但这里有一个巨大的陷阱&#xff1a;虽然它按顺序“启动”任务&#xff0c;但它不会等待异步操作&#xff08;如 Prom…

作者头像 李华