编程语言研究书库

TypeScript 是什么、为什么需要它 — 从动态类型的坑说起

TypeScript 是什么、为什么需要它 — 从动态类型的坑说起

这一章讲三件事: TypeScript 到底是什么(它其实不是一种「能跑的语言」); 为什么 JavaScript 的灵活会在大项目里变成灾难;以及它补齐的面向对象能力有什么用。 读完你会拿到全书的出发点:后面十五章,全是在这一章换来的类型安全之上盖楼。

1. 先看一个真实的坑:两个数相加,结果不是数

JavaScript 是全世界用得最多的语言之一。书里引了 GitHub 的统计: 到 2025 年,JavaScript 仍是 GitHub 上近 80% 新仓库在用的六种核心语言之一; 而就在 2025 年 8 月,TypeScript 的使用者数量超过了 Python 和 JavaScript 本身, GitHub 称这是十多年来最大的一次语言格局变动1。

这么多人用,问题不在「人少」,在语言本身的脾气。看原书的第一个例子:

let a = 5;
let b = '6';
console.log(a + b);

你想让两个数相加,期望打印 11。但运行结果是 56。

为什么?因为 b 其实是个字符串,JavaScript 的 + 号看见两边有一边是字符串, 就把另一边也转成字符串、拼接起来2。5 被悄悄变成了 '5',拼在 '6' 前面。 这种「悄悄替你转换类型」的行为叫类型强制(type coercion)——它平时像便利, 出错时像背刺:程序不报错,只是结果不对,而且这个错一直潜伏到你真正用它的时候。

再看第二个坑。原书有一个判断用户能不能开车的函数,它读用户的 age 字段:

function canDrive(usr) {
    if (usr.age >= 16) { /* 允许开车 */ }
}
const tom = { name: "tom" }   // 注意:tom 没有 age 字段
canDrive(tom);

tom 对象压根没有 age。JavaScript 不会拦你,运行到 usr.age 时它拿到一个特殊的空值 undefined,这个空值在比较里被当成「假」,于是一个 30 岁但字段写错名字的用户, 会被判成不许开车——程序照样不报错3。

这两类错有一个共同点:都要等程序真正跑起来、跑到那一行,才暴露。 小脚本无所谓,大项目里这样的行有几十万,你不可能每行都跑到。

2. 顶层全景:TypeScript 是语言加编译器,而且代码永远不在机器上跑

原书给 TypeScript 下的定义很干脆——它是两项技术4:

  你写的 .ts 文件
      │
      ▼
  TypeScript 编译器(tsc)── 两件事同时发生 ──┐
      │                                   │
      ├─ ① 检查:类型对不对?不对就报错,不出成品  │
      ▼                                   │
  ② 翻译:产出纯 JavaScript(.js 文件) ◄──────┘
      │
      ▼
  浏览器或 Node 运行的是这份 .js —— 机器上从来没有 TypeScript

  图说:TypeScript 是「开发期」技术。它管的是写完代码到代码运行之间的那段时间。

TypeScript 是开发时技术——这是全书最该记住的一句话。机器上没有任何 TypeScript 在跑; 编译器把 .ts 翻译成 .js,跑的是 JavaScript5。这个翻译过程有个专门的名字, 叫转译(transpilation):和编译不同,它不是变成机器码,而是从一种语言变成另一种语言6。

所以它的价值也全在开发期:错误在你敲代码的时候就被标出来,而不是等用户替你发现。 书里说,这等于给开发流程加了纪律——错误在开发阶段就被迫修掉7。

插曲:第一次转译的完整一圈(原书手把手部分)

在讲类型之前,书里先带读者把工具链走了一遍,这一圈本身就是现代 Node 项目的 标准开机动作,值得原样记住:

  npm init            → 生成 package.json(项目的「户口本」:名字、版本、
                        scripts 脚本区、dependencies 依赖区)
  npm install -g typescript(书里为示例选了全局安装;实际项目应装到项目里)
                      → 获得 tsc 命令(Mac 上多半要加 sudo)
  建 string-vs-number.ts → 注意 .ts 扩展名:这是 TypeScript 的专属扩展名,
                        编译器靠它认出要处理的文件
  tsc string-vs-number.ts → 同时做两件事:检查类型 + 产出同名的 .js
  node string-vs-number.js → 跑的是翻译产物,不是 .ts

原书还交代了一个后续章节反复用的细节:package.json 里有两个区—— scripts 存命令快捷方式(npm run xxx 的 xxx),dependencies 存项目依赖; TypeScript 要求 5.5 或更高8。本书后面的每一章,开机动作都是这三板斧的变体。

3. 核心原理一:静态类型 = 把「这个变量里应该装什么」写在明处

原书对「类型」的定义朴素得惊人:类型就是一套可复用的规则,用来描述一个对象或变量9。

JavaScript 是动态类型:变量不需要声明类型,装过数字的变量随时可以改装字符串。 TypeScript 走另一条路——静态类型:创建变量时就把类型标明,之后不许变10。 标法的专用名词叫类型注解(type annotation),就是在变量后面写 : number 这样的字。

回到第 1 节的坑,各看一眼修法(这就是本章的主线例子:同一个用户年龄判断, 从裸 JavaScript 一路改到类型安全的版本):

  主线例子:判断用户能不能开车(canDrive)

  第 ① 步  裸 JavaScript
    canDrive(tom) 照常跑,tom 没有 age 也不报错 → 运行时才爆

  第 ② 步  给变量加类型注解
    let b: number = '6'
    → 编译器当场报错:Type 'string' is not assignable to type 'number'
    → 数字字符串拼接那一类错,在写代码阶段就被消灭

  第 ③ 步  用 interface 给「用户」这个形状定合同
    interface User { name: string; age: number }
    function canDrive(usr: User) { ... }
    → 调用 canDrive(tom) 时,tom 缺 age 字段,编译器再次报错
    → 原话:Property 'age' is missing in type '{ name: string; }' but required in type 'User'
    → 对象缺字段那一类错,同样在运行之前被消灭

第 ③ 步里出现了一个新东西 interface User。原书把它叫作合同(contract): 一个不带实现、只定形状的类型——任何号称是 User 的对象,必须有 name 和 age 两个字段11。 函数参数一标 : User,编译器就会自动检查每一个调用点传进来的对象合不合合同。 这就是静态类型真正的威力来源:不是你写了一次检查,而是每个调用点都被自动检查了一遍。

书里还补了一个时间坐标:从 TypeScript 6.0 起,tsconfig.json 里的 strict (最严格的一档检查)默认开启,新项目开箱就是最高强度检查12。

4. 核心原理二:面向对象的四根支柱,JavaScript 缺了两根半

第二个要补的能力是面向对象编程(OOP)。原书列的现代四支柱是: 封装、抽象、继承、多态13。逐个看 JavaScript 的缺口在哪、TypeScript 怎么补。

先给坐标:2025 年的语言格局

原书开篇用了一组数字立论,值得原样记住量级:GitHub 上近 80% 的新仓库在用的 六种核心语言里有 JavaScript;而 2025 年 8 月,TypeScript 的使用者数量超过 Python 和 JavaScript 本体登顶,年增长超 100 万贡献者、增速 66.63%——GitHub 称之为 十多年来最大的一次语言格局变动14。这本书的存在本身就是这个变动的注脚: 不是 JavaScript 不行了,而是大项目多到「灵活」本身成了最大的成本。

封装:把数据藏进容器,只留受控的口

封装换个说法就是信息隐藏:把数据连同操作它的方法装进一个叫类(class)的容器, 容器外的东西不能直接碰数据,只能走类提供的入口15。 TypeScript 控制入口的工具叫访问修饰符:private 只允许类内部访问, public 允许内外访问;再配合 getter/setter(读取器/写入器), 类就握住了「谁能读、谁能改、怎么改」的决定权16。

  主线例子的支线:Encapsulator 类

  class Encapsulator {
      private name: string                    ← 字段藏起来了
      get getName(): string { return this.name }   ← 只读口
      set setName(name: string) { this.name = name } ← 只写口
  }
  new Encapsulator("John").name       → 编译器拒绝:private
  new Encapsulator("John").getName    → 拿到 "John"

书里有个细节值得记住:原生 JavaScript 现在也能用 # 前缀藏字段了, 这本书通篇优先用 JavaScript 标准语法——TypeScript 刻意贴着 JavaScript 的官方标准 (ECMAScript)走,凡是 JavaScript 已经能表达的,就不再用 TypeScript 的私有写法17。

四支柱与 Java 系语言的对照(原书的隐含读者背景)

书里反复强调「几乎所大型应用语言(Java、C++、C#)都提供静态类型和 OOP」, 这句话其实是给有 Java/C# 背景的读者一颗定心丸,也给了所有人一张对照表: 封装(private/public + getter/setter)、抽象(interface/abstract class)、 继承(extends/super)、多态(对接口编程)在 TypeScript 里一样不少, 且语法刻意贴近这些语言;真正的差异在两条:TypeScript 的 private 只在编译期生效 (Java 的运行时反射语义不同),以及结构类型让「是否写了 implements」成为可选而非必须 (03 章的结构类型会把这个差异讲透)。

抽象:只露签名,不露实现

抽象和封装是亲戚:把「数据怎么管」藏起来,对外只给一个简化的接口。 目的叫松耦合——管一摊数据的代码和别的代码互不依赖, 改一边不伤另一边18。机制是 interface(接口)或抽象类:像壳, 只暴露成员的名字和类型,不写实现19。JavaScript 不支持接口和抽象类, TypeScript 两者都支持20。

代码上就是第 3 节那个 interface User,再加一个实现它的类:

interface User { name: string; age: number; canDrive(): void }
class Person implements User { /* 把 canDrive 真正实现出来 */ }
const john: User = new Person("john", 15);

implements(实现)这个词是合同的另一面:接口声明「必须有这些成员」, 类签字画押负责把它们做出来21。于是调用方只对着 User 这个壳写代码, 底下换成任何实现都无所谓——这就是松耦合的实际形状。

继承:写一次,处处复用

继承解决的是代码复用。书里的例子:商店卖自行车、卡车、摩托车, 每样都写一遍 id、description、price 太浪费;抽出一个基类 Item 装公共部分, Bicycle 用 extends(继承)关键字直接拿来,再添自己特有的 wheelCount22。

JavaScript 的继承靠原型继承(prototypal inheritance): 同一类的所有实例共享同一个核心对象(原型),成员挂原型上,实例各自引用。 省内存,但表达力有限23。TypeScript 的类不但能继承类,还能继承接口和抽象类, 复用的路子多得多24。

多态:对「一般类型」写代码,运行时再定「具体是谁」

多态和继承配套:对着基类型写代码,具体执行哪个实现,运行时才知道; 调用方不需要、也不该关心拿到的到底是哪个子类25。 JavaScript 靠动态类型天然支持这一点,但它缺编译期把关: 你传一个缺了方法的对象进来,没有任何东西拦你,只能等运行到那行才炸。 TypeScript 补的正是这一环——接口和类型约束让不匹配在代码运行前就被抓住26。

5. 作者的判断与证据

这一章几乎全是「共识级」的说法,作者的推理链条是:JavaScript 为简单场景而生 → 大项目需要静态类型和完整 OOP → 多年里出现过好几种补法,TypeScript 是被采用最广、 活得最久的那个27。证据主要是两类:

  • 使用量的证据(GitHub Octoverse 2025 统计,引在第 1 节)——TypeScript 超车的规模和增速1;
  • 演示性的证据——本章所有类型错误都是编译器真实报出的原文(如 Type 'string' is not assignable to type 'number')2,这类证据是「可复现的」, 读者自己敲一遍就能看到。

有一处是作者的判断而非事实:「静态类型让代码更清晰、更少 bug、更好重构」是行业广泛接受的 主张,但书里给的是例证,不是对照实验数据。我们认可这个判断,但见第 6 节的边界。

6. 边界与局限

  • 只讲了「为什么」,没讲「怎么用好」。 泛型(generics)在第 4 节末尾被预告了, 正文说「要等第 2 章学完才能讲透多态的完整例子」,本章的多态只有文字没有完整代码25。
  • 严格的代价没展开。 strict 默认开启意味着大量「原本能跑」的 JavaScript 写法 在 TypeScript 下要改写;迁移存量项目的痛苦,这一章只字未提。
  • 时间坐标:2026 年。 书里所有版本号(TypeScript 6、Node 24)是当时的; 这类数字会过期,机制不会。后文各章遇到具体版本我们都会标时点。

判断(我们的,不是书里的): 这一章真正的教学价值不在 OOP 四支柱的讲解 (任何教材都有),而在它先让你踩到动态类型的坑,再给解药的顺序—— 类型注解和 interface 合同出现之前,你已经亲眼见过两类运行时才爆的错。 这个顺序值得在教任何一门静态类型语言时复用。 如果错,会错在: 「踩坑再给药」对有 JavaScript 实战经验的人有效; 对完全的新手,坑本身可能读不出痛感,顺序优势会打折。

7. 可带走的

  1. TypeScript = 一门语言 + 一个编译器;跑在机器上的永远是它翻译出来的 JavaScript。
  2. 转译(transpilation)是从一种语言到另一种语言,不是编译成机器码。
  3. 动态类型的两大暗坑:+ 的类型强制拼接、对象缺字段拿到 undefined 被当「假」。
  4. 静态类型把这两类错从「运行时爆炸」提前到「写代码时报红」。
  5. 类型注解 : number 是显式声明;interface 是给对象形状定的合同。
  6. OOP 四支柱:封装(信息隐藏 + 访问修饰符)、抽象(接口/抽象类、松耦合)、 继承(extends 复用)、多态(对一般类型写代码,编译期把关)。
  7. TypeScript 贴着 ECMAScript 标准走;JavaScript 能表达的(如 # 私有字段), 这本书优先用 JavaScript 写法。
  8. 从 TypeScript 6.0 起 strict 默认开启——新项目默认最严格检查。

8. 原文地图

主题原书章原文位置
GitHub 统计与语言格局变动Chapter 1: Understanding TypeScripttext/04-ch01-chapter-1-understanding-typescript.txt:13(搜「overtook both Python and JavaScript」)
TypeScript = 语言 + 编译器同上text/04-ch01-chapter-1-understanding-typescript.txt:93(搜「two distinct but related」)
开发时技术、转译同上text/04-ch01-chapter-1-understanding-typescript.txt:109(搜「development-time technology」)
类型强制拼接的坑同上text/04-ch01-chapter-1-understanding-typescript.txt:480(搜「type coercion」)
类型注解与编译器报错同上text/04-ch01-chapter-1-understanding-typescript.txt:516(搜「Type Annotation」) · text/04-ch01-chapter-1-understanding-typescript.txt:526(搜「not assignable」)
strict 默认开启同上text/04-ch01-chapter-1-understanding-typescript.txt:653(搜「strict flag」)
interface 即合同同上text/04-ch01-chapter-1-understanding-typescript.txt:691(搜「contract」)
OOP 四支柱同上text/04-ch01-chapter-1-understanding-typescript.txt:882(搜「Encapsulation」)
抽象与松耦合同上text/04-ch01-chapter-1-understanding-typescript.txt:1099(搜「Loose coupling」)
JavaScript 不支持接口与抽象类同上text/04-ch01-chapter-1-understanding-typescript.txt:1119(搜「does not support interfaces」)
原型继承同上text/04-ch01-chapter-1-understanding-typescript.txt:1259(搜「Prototypal」)
多态缺编译期把关同上text/04-ch01-chapter-1-understanding-typescript.txt:1397(搜「compile-time enforcement」)

Footnotes

  1. 出处:「Chapter 1: Understanding TypeScript」第 13 段(text/04-ch01-chapter-1-understanding-typescript.txt:13,搜「overtook both Python and JavaScript」)。原文含具体数字:增长 66.63%、年增超 100 万贡献者。引的是 GitHub Octoverse 2025 报告,属于书里转述的第三方统计。 ↩ ↩2

  2. 出处:「Chapter 1: Understanding TypeScript」第 480 段(text/04-ch01-chapter-1-understanding-typescript.txt:480,搜「type coercion」)。例子代码在原文第 410–412 段(text/04-ch01-chapter-1-understanding-typescript.txt:410,搜「let a = 5」)。 ↩ ↩2

  3. 出处:「Chapter 1: Understanding TypeScript」第 572 段与第 572 段(text/04-ch01-chapter-1-understanding-typescript.txt:572,搜「function canDrive」;:601,搜「resolve to false」)。「falsy」的提法见第 605 段(搜「falsy」)。 ↩

  4. 出处:「Chapter 1: Understanding TypeScript」第 93–99 段(text/04-ch01-chapter-1-understanding-typescript.txt:93,搜「two distinct but related」)。 ↩

  5. 出处:「Chapter 1: Understanding TypeScript」第 109–113 段(text/04-ch01-chapter-1-understanding-typescript.txt:109,搜「development-time technology」)。「没有任何 TypeScript 代码在机器上运行」是原文第 111 段的原话(搜「no TypeScript code ever runs」)。 ↩

  6. 出处:「Chapter 1: Understanding TypeScript」第 117–123 段(text/04-ch01-chapter-1-understanding-typescript.txt:117,搜「transpilation」)。 ↩

  7. 出处:「Chapter 1: Understanding TypeScript」第 105 段(text/04-ch01-chapter-1-understanding-typescript.txt:105,搜「discipline」)。 ↩

  8. 出处:「Chapter 1: Understanding TypeScript」第 259–340 段(text/04-ch01-chapter-1-understanding-typescript.txt:294,搜「section called」;:338,搜「5.5 or later」)。 ↩

  9. 出处:「Chapter 1: Understanding TypeScript」第 362 段(text/04-ch01-chapter-1-understanding-typescript.txt:362,搜「of reusable rules that describe an objec」)。 ↩

  10. 出处:「Chapter 1: Understanding TypeScript」第 364–380 段(text/04-ch01-chapter-1-understanding-typescript.txt:364,搜「dynamically typed」;:380,搜「up front」)。 ↩

  11. 出处:「Chapter 1: Understanding TypeScript」第 689–691 段(text/04-ch01-chapter-1-understanding-typescript.txt:689,搜「without an implementation」)。 ↩

  12. 出处:「Chapter 1: Understanding TypeScript」第 653 段(text/04-ch01-chapter-1-understanding-typescript.txt:653,搜「strict flag」)。这是 2026 年时点(TypeScript 6.0)的情况。 ↩

  13. 出处:「Chapter 1: Understanding TypeScript」第 880–888 段(text/04-ch01-chapter-1-understanding-typescript.txt:880,搜「widely accepted modern list」)。 ↩

  14. 出处:「Chapter 1: Understanding TypeScript」第 11–19 段(text/04-ch01-chapter-1-understanding-typescript.txt:13,搜「overtook both Python and JavaScript」)。数字为书中转述的 GitHub Octoverse 2025 报告,属第三方统计。 ↩

  15. 出处:「Chapter 1: Understanding TypeScript」第 894–908 段(text/04-ch01-chapter-1-understanding-typescript.txt:894,搜「information hiding」)。 ↩

  16. 出处:「Chapter 1: Understanding TypeScript」第 918–924 段(text/04-ch01-chapter-1-understanding-typescript.txt:918,搜「private」)。 ↩

  17. 出处:「Chapter 1: Understanding TypeScript」第 934–944 段(text/04-ch01-chapter-1-understanding-typescript.txt:936,搜「symbol, and we will prefer this JavaScri」;:938,搜「ECMAScript」)。 ↩

  18. 出处:「Chapter 1: Understanding TypeScript」第 1095–1103 段(text/04-ch01-chapter-1-understanding-typescript.txt:1099,搜「Loose coupling」)。 ↩

  19. 出处:「Chapter 1: Understanding TypeScript」第 1107–1113 段(text/04-ch01-chapter-1-understanding-typescript.txt:1107,搜「interface or abstract class」)。 ↩

  20. 出处:「Chapter 1: Understanding TypeScript」第 1119 段(text/04-ch01-chapter-1-understanding-typescript.txt:1119,搜「does not support interfaces」)。 ↩

  21. 出处:「Chapter 1: Understanding TypeScript」第 1195 段(text/04-ch01-chapter-1-understanding-typescript.txt:1195,搜「implements」)。 ↩

  22. 出处:「Chapter 1: Understanding TypeScript」第 1237–1247 段与 1297–1321 段(text/04-ch01-chapter-1-understanding-typescript.txt:1237,搜「code reuse」;:1307,搜「extends Item」)。 ↩

  23. 出处:「Chapter 1: Understanding TypeScript」第 1255–1269 段(text/04-ch01-chapter-1-understanding-typescript.txt:1259,搜「Prototypal」)。 ↩

  24. 出处:「Chapter 1: Understanding TypeScript」第 1271 段(text/04-ch01-chapter-1-understanding-typescript.txt:1271,搜「inherit from interfaces」)。 ↩

  25. 出处:「Chapter 1: Understanding TypeScript」第 1387–1391 段与第 1387 段(text/04-ch01-chapter-1-understanding-typescript.txt:1387,搜「general type」;:1401,搜「don't know enough」)——原文明说本节讲不透,完整例子留到第 2 章。 ↩ ↩2

  26. 出处:「Chapter 1: Understanding TypeScript」第 1397–1399 段(text/04-ch01-chapter-1-understanding-typescript.txt:1397,搜「compile-time enforcement」)。 ↩

  27. 出处:「Chapter 1: Understanding TypeScript」第 139–145 段(text/04-ch01-chapter-1-understanding-typescript.txt:139,搜「beyond its original scope」;:145,搜「most widely adopted and enduring」)。 ↩

On this page