TypeScript---变量声明:让每一次命名都蕴含意义
目录
写代码的那一刻,当你敲下:
let message = "Hello, world!";
你其实在做两件事:给这个名字一个位置,并给这个位置一个承诺。
名字要被使用,承诺要被信任。
在 JavaScript 的时代,变量声明也许是随意的、模糊的,而在TypeScript的世界里,变量声明被赋予了更多的意义:作用域明确、赋值受控、未来可读。
本文基于官方文档,从 var、let、const 三种声明方式出发,深入解析它们的语法、作用域、使用场景以及为何 TypeScript 倾向推荐 let 与 const。
每一个名字背后,都值得我们认真看待。
一、概览:变量声明的三种关键方式
在 TypeScript(继承ES的语法与语义)中,变量声明主要有三种关键形式:
| 关键字 | 作用域 | 是否可重新赋值 | 是否可重复声明 | 典型情境 |
|---|---|---|---|---|
| var | 函数级作用域 | √ | √ | 兼容旧代码、简单脚本 |
| let | 块级作用域 | √ | × | 普通变量、自定义值 |
| const | 块级作用域 | × | × | 常量、配置、不可变绑定 |
接下来,我们分别从var、let、cosnt 三个层面,依次细说它们的特点、问题与用法。
二、var:曾经的主角,却留下隐患
在JavaScript传统时代,变量声明几乎总是用:
var a = 10;
这是开发者习以为常的方式。手指敲下var,变量出现了,赋值立刻完成。TypeScript因为是JavaScript的超集,也支持它。
然而,正因为“方便”,它同时带来了不少潜在问题:
2.1 作用域规则的模糊
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
}
return x;
}
f(true); // 返回 10
f(false); // 返回 undefined
在这个例子里,变量x虽然写在if块里,但在函数体外依然可访问。这是因为var是函数作用域,而不是块作用域。
函数作用域:只要在同一个函数里,不管在if、for、while里声明,整个函数都能访问到它。
块作用域:只在它所在的{}内可访问。出了大括号就不能访问。
这种作用域的不明确,意味着变量“冒出”在你意想之外的地方,隐藏着隐患。
2.2 重复声明与覆盖容易
function sumMatrix(matrix: number[][]) {
var sum = 0;
for (var i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
注意内部for循环中使用var i再次声明。因为var不区分块里块外,这里是同一个i,覆盖前面的循环控制,极易造成逻辑错误。
2.3 捕获变量的怪异行为
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100 * i);
}
// 输出十次 “10”
许多开发者期望输出0,1,2,...9,但由于var i的作用域是同一个,循环结束后i为10,所有回调函数打印的都是10。
这些问题让var的使用变得危险。TypeScript文档指出,let与const引入,是为了“避免在 JavaScript里常见一些问题”。
三、let:块级作用域的变量声明
引入let是对var的改进。变量声明不再模糊地漂浮,而是有“块(block)”这个边界。
let hello = "Hello!";
语法上与var类似,但语义上截然不同。
3.1 块作用域(block-scoped)
function f(input: boolean) {
let a = 100;
if (input) {
let b = a + 1;
return b;
}
// Error: 'b' 不存在于此处
return b;
}
在此例中,b的作用域限于if块内。超出那块就访问不到。相比之下,若使用var b,则可能访问得到,为undefined或旧值。
3.2 暂时性死区(TDZ)
在let声明的变量,在其被声明之前,处于所谓 “暂时性死区” 状态——你不能读取或写入它。
a++; // 不合法:在声明前使用
let a;
虽然变量“存在”于其被声明的作用域内,但直到声明语句运行之前,不可使用。官方称这是let等块级声明的一个特点。
3.3 不能重复声明(同一作用域内)
let num = 1;
// let num = 2; // 编译错误:重复声明
相比var,let在同一作用域中重复声明会报错,从而避免名字被意外覆盖。
3.4 捕获变量在循环中的改进
使用let声明循环变量,每次迭代都会产生新的作用域环境,从而避免了var的“所有回调共享同一个变量”的问题:
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100 * i);
}
// 输出:0,1,2,...9
官方手册也明确指出,这种行为是let的优势之一。
3.5 何时使用let
官方建议:在你预计变量会被重新赋值的情况下,使用let。
也就是说,当变量需要变化、作用域不需要为全局或函数级、你希望有块级边界的时候,用let是合理的选择。
四、const:绑定不变,是另一种承诺
如果let是“可以变化的变量”,那么 const 则是“绑定后不再改变”的变量。
const numLivesForCat = 9;
const与let拥有相同的作用域规则,但最大的差别在于:绑定之后不能再重新赋值。
4.1 不可重赋与可变内容的区别
值得注意的是,const 并不保证变量引用的值完全不可变。它只保证“变量标识符绑定后不再指向新的值”。例如:
const kitty = {
name: "Aurora",
numLives: 9
};
kitty.name = "Rory"; // 合法
kitty = { name: "Danielle", numLives: 9 }; // 错误:不能重新赋值
正如官方文档所述,这并不意味着对象成员不能改变,只是不允许重绑定。
4.2 必须初始化
使用const声明变量时,必须立即进行初始化;不能先声明后赋值。若做类似:
const num: number;
// num = 10; // 错误:const 声明必须初始化
类型系统会报错。
4.3 何时使用const
官方建议采用“最小权限原则(least privilege)”:如果你确定一个变量在以后不会被重赋值,那么优先使用 const。
最小权限原则:让代码中的变量、模块、函数、用户仅拥有执行任务所需的最小操作权限。
这样不仅可读性更好,也让代码逻辑更可预测。
五、let vs const:抉择的微妙哲学
当你面对“当前变量是否会被修改”这个问题时,其实是在做一种思考模式的切换:是“变化”还是“稳固”?是“可能再赋值”还是“绑定一次”?TypeScript鼓励我们选择更可预测、更可靠的方式。
大致建议如下:
- 若你预期变量会被重赋值,使用let。
- 若你一旦赋值就不会变,使用const。
- 避免默认使用var,除非你在兼容旧代码或脚本环境。
- 在代码审查、团队合作中,const更能显示“这里不会变化”的承诺,使同事或未来维护者一目了然。
这一选择,看似微小,却折射出更深的编程态度:定义越早、限制越明确,未来越可控。
六、为何官方推荐let与const多于var
从以上分析我们可以归纳出几个关键原因:
- 作用域更清晰:let与const 使用块级作用域,消除var导致的作用域“泄露”问题。
- 避免重复声明与覆盖:let防止同一作用域重复声明,减少意外覆盖变量的风险。
- 提高代码可预测性与可读性:当看到const,你马上知道这个绑定不会再改变;看到let,你知道它可能会。
- 降低潜在bug:彻底避免var在循环、异步回调中导致的奇怪行为(如变量捕获问题)
- 团队协作与维护友好:明确变量是否会被修改,有助于团队成员理解、未来维护也更容易。
因此,TypeScript 文档明确指出:“多数手册内容使用 let 声明。”
七、总结:让每一个名字都值得被信任
当你选择const,你在说:“这个值一旦设定,就不会动——你可以放心使用它。”
当你选择let,你在说:“这个值可能改变——我会留意它的变化。”
而如果你还在用var,可能就意味着:“我暂时不关心它在哪里被访问、被修改、被捕获。”
这种态度,直接体现在代码可读性、可维护性、健壮性上。
TypeScript 的变量声明机制,不只是让你“能”写,更让你“应该”写得更好。
更多推荐


所有评论(0)