TypeScript 的诞生
Anders Hejlsberg 与大型 JS 项目的困境
2010 年,微软内部面临一个严峻挑战:团队正在用 JavaScript 开发 Visual Studio 的 Web 版本(后来的 Monaco Editor,即今天 VS Code 的编辑器核心)。这个项目代码量庞大,涉及数十名工程师协作,而 JavaScript 的动态类型特性让这个项目变成了噩梦。
工程师们发现,在大型代码库中,一个函数改了参数名,调用方根本不知道;一个对象新增了必填字段,所有漏掉填写的地方只有在运行时才会暴露;IDE 的代码补全功能因为无法确定类型而形同虚设。
这个项目的负责人正是 Anders Hejlsberg——他同时也是 Turbo Pascal 之父、Delphi 之父和 C# 之父。他决定为 JavaScript 设计一套类型系统,这就是 TypeScript 的起点。
TypeScript 关键时间线
今天谁在用 TypeScript?
TypeScript 已成为现代前后端开发的标准语言:
- 框架与库:React(.d.ts)、Vue 3(TS 重写)、Angular(TS 唯一)、NestJS、Prisma、Zod 等
- 工程化工具:VS Code(TS 编写)、ESLint、Prettier、Vite、Webpack 插件
- 云服务 SDK:AWS SDK v3、Azure SDK、Google Cloud 客户端库全面 TS 化
- StackOverflow 调查:连续多年位列「最受欢迎编程语言」前五,2023 年超越 Python 成为第一
JavaScript 的动态类型痛点
典型的运行时错误场景
以下是纯 JavaScript 中常见的类型相关错误,这些错误只有在运行时才能发现:
// 场景1:函数参数类型错误
function formatPrice(price) {
return price.toFixed(2) + ' 元';
}
formatPrice('100'); // 运行时错误:String 没有 toFixed 方法
formatPrice(null); // TypeError: Cannot read property 'toFixed' of null
// 场景2:对象属性拼写错误
const user = { name: '张三', age: 28 };
console.log(user.nname); // undefined(没有报错!潜在的 bug)
// 场景3:重构后遗留的错误调用
// 原来是 getUserById(id),重构后改为 getUser(userId)
const user2 = getUserById('123'); // 运行时才发现函数不存在
// TypeScript 的处理方式
function formatPrice(price: number): string {
return price.toFixed(2) + ' 元';
}
formatPrice('100'); // ❌ 编译错误:Argument of type 'string' is not assignable to type 'number'
formatPrice(null); // ❌ 编译错误:Argument of type 'null' is not assignable to type 'number'
// 对象属性访问的类型安全
interface User {
name: string;
age: number;
}
const user: User = { name: '张三', age: 28 };
console.log(user.nname); // ❌ 编译错误:Property 'nname' does not exist on type 'User'
TypeScript 的核心价值在于将错误发现的时机「左移」(Shift Left):从生产环境运行时 → 测试阶段 → 本地开发编译时。错误发现得越早,修复成本越低。研究表明,TypeScript 可以在编译时捕获大约 15% 的 JavaScript bug(来自 Airbnb 内部研究数据)。
TypeScript 编译过程
tsc:TypeScript 编译器
tsc(TypeScript Compiler)是 TypeScript 的官方编译器。它的工作分为两个阶段:
TypeScript 的类型只存在于编译时。编译后的 JavaScript 中没有任何类型信息——interface、type alias、类型标注全部被删除。这意味着:
1. TypeScript 不增加任何运行时开销
2. 运行时的类型验证(如 API 响应数据)仍然需要用 Zod/Yup 等库手动实现
3. 说「TypeScript 是 JavaScript」是准确的——所有 TS 代码最终都运行为 JS
编译流程图
结构化类型系统(Duck Typing)
名义类型 vs 结构化类型
TypeScript 使用结构化类型系统(Structural Type System),而不是 Java/C# 那样的名义类型系统(Nominal Type System)。这是 TypeScript 设计中最重要的决策之一。
// 结构化类型系统示例
interface Point2D {
x: number;
y: number;
}
interface Vector2D {
x: number;
y: number;
}
// Point2D 和 Vector2D 结构完全相同,TypeScript 认为它们兼容
function printPoint(p: Point2D) {
console.log(`(${p.x}, ${p.y})`);
}
const vec: Vector2D = { x: 1, y: 2 };
printPoint(vec); // ✅ OK!结构兼容
// 更强的例子:匿名对象字面量
printPoint({ x: 3, y: 4, z: 5 }); // ✅ OK!额外属性在赋值给变量时 OK
// (注意:直接作为对象字面量传参时会有「额外属性检查」,这是另一个话题)
// 名义类型系统则需要显式实现接口才行
// TypeScript 无需 implements Point2D 就能使用
为什么结构化类型适合 TypeScript?
JavaScript 本身就使用结构化类型(任何有相同属性的对象都能当同一种类型使用),TypeScript 的结构化类型系统完美地与 JS 的编程习惯匹配。如果 TypeScript 使用名义类型,大量现有的 JS 代码模式将无法正确类型化。
安装与环境配置
安装 TypeScript
# 全局安装 tsc(不推荐用于项目)
npm install -g typescript
tsc --version # Version 5.x.x
# 推荐:项目本地安装(每个项目独立版本)
npm init -y
npm install --save-dev typescript
# 运行本地 tsc
npx tsc --version
# 生成 tsconfig.json
npx tsc --init
ts-node 与 tsx:直接运行 TS 文件
# ts-node:传统方式,带完整类型检查
npm install --save-dev ts-node
npx ts-node src/index.ts
# tsx:更快,跳过类型检查(适合开发时热重载)
npm install --save-dev tsx
npx tsx src/index.ts
npx tsx watch src/index.ts # 文件变化时自动重启
# 也可以直接用 Node.js 22.6+ 的原生 TS 支持(实验性)
node --experimental-strip-types src/index.ts
第一个 TypeScript 文件
// src/index.ts
// 类型标注:变量
const greeting: string = 'Hello, TypeScript!';
const year: number = 2024;
const isActive: boolean = true;
// 类型推断:TypeScript 自动推导类型,无需手写
const autoString = '这是 string 类型'; // 推断为 string
const autoNumber = 42; // 推断为 number
const autoArray = [1, 2, 3]; // 推断为 number[]
// 函数类型标注
function greet(name: string, age: number): string {
return `你好,${name}!你 ${age} 岁了。`;
}
// 接口定义对象形状
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// 使用接口
function displayUser(user: User): void {
console.log(`[${user.id}] ${user.name} <${user.email}>`);
}
const myUser: User = {
id: 1,
name: '张三',
email: 'zhangsan@example.com',
createdAt: new Date(),
};
displayUser(myUser);
编译为 JavaScript
# 编译单个文件
npx tsc src/index.ts
# 生成 src/index.js(类型信息被擦除)
# 使用 tsconfig.json 编译整个项目
npx tsc
# 只做类型检查,不生成 JS 文件(推荐 CI 使用)
npx tsc --noEmit
# 监视模式:文件变化时自动重新编译
npx tsc --watch
在实际项目中,tsc 的编译速度较慢(因为它会执行完整的类型检查)。大多数现代工具链(Vite、esbuild、SWC)会跳过类型检查、只做语法转译,速度快 10-100 倍。推荐的做法是:开发和构建用快速工具(Vite/esbuild),CI 流水线中专门运行 tsc --noEmit 进行完整类型检查。