一个只学过 Java 的人,第一次打开前端代码,大概会是这个表情:
let、const、var到底用哪个?- 为什么函数可以写成
=>这种箭头?undefined和null有啥区别?- 最要命的:为什么一个变量,一会儿是数字、一会儿又变成字符串了? 类型呢?
你可能会慌:「这语言也太随便了吧。」
但先别急着否定。TypeScript 恰恰是来「拯救」你的 ------它就是 JavaScript 加上了一套你最熟悉的静态类型系统。换句话说,你以前在 Java 里养成的「先定类型、再写逻辑」的好习惯,在 TypeScript 里能原封不动地用回来。
这篇就是为「只会 Java、没碰过 JS 」的你量身定制的。不背语法手册,而是从零搭一个 TODO 小项目,边写边把 TypeScript 的核心概念一个个吃下来。等你写完这个项目,TypeScript 也就入门了。
一、先搞清楚三件事:JS / TS / 运行时
学 TS 之前,必须先把三个容易混的东西分清楚。
1.1 JavaScript 是什么?
JavaScript(JS)是浏览器里唯一能直接跑的语言 ,也是 Node.js 服务端的语言。它有一个让 Java 程序员「很难受」的特点:动态类型------变量没有固定类型,同一变量可以随时换成别的类型:
javascript
// JavaScript:变量没有类型约束
let x = 10;
x = "hello"; // 居然可以!数字变成了字符串
x = [1, 2, 3]; // 又变成了数组
这在 Java 里是绝对不允许 的(int 就是 int)。动态类型带来了灵活性,也带来了隐患------很多 bug 要等到运行时才暴露。
1.2 TypeScript 是什么?
TypeScript(TS)是微软出的语言,它的定义就一句话:
TypeScript = JavaScript + 类型系统。
它是 JS 的「超集 」------所有合法的 JS 都是合法的 TS,同时 TS 额外提供了类型标注、interface、泛型等能力。写完 TS 后,会被编译(transpile)成纯 JS 再去运行。
#mermaid-svg-MN0pZTvYjXhNpWJr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MN0pZTvYjXhNpWJr .error-icon{fill:#552222;}#mermaid-svg-MN0pZTvYjXhNpWJr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MN0pZTvYjXhNpWJr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MN0pZTvYjXhNpWJr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MN0pZTvYjXhNpWJr .marker.cross{stroke:#333333;}#mermaid-svg-MN0pZTvYjXhNpWJr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MN0pZTvYjXhNpWJr p{margin:0;}#mermaid-svg-MN0pZTvYjXhNpWJr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr .cluster-label text{fill:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr .cluster-label span{color:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr .cluster-label span p{background-color:transparent;}#mermaid-svg-MN0pZTvYjXhNpWJr .label text,#mermaid-svg-MN0pZTvYjXhNpWJr span{fill:#333;color:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr .node rect,#mermaid-svg-MN0pZTvYjXhNpWJr .node circle,#mermaid-svg-MN0pZTvYjXhNpWJr .node ellipse,#mermaid-svg-MN0pZTvYjXhNpWJr .node polygon,#mermaid-svg-MN0pZTvYjXhNpWJr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MN0pZTvYjXhNpWJr .rough-node .label text,#mermaid-svg-MN0pZTvYjXhNpWJr .node .label text,#mermaid-svg-MN0pZTvYjXhNpWJr .image-shape .label,#mermaid-svg-MN0pZTvYjXhNpWJr .icon-shape .label{text-anchor:middle;}#mermaid-svg-MN0pZTvYjXhNpWJr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MN0pZTvYjXhNpWJr .rough-node .label,#mermaid-svg-MN0pZTvYjXhNpWJr .node .label,#mermaid-svg-MN0pZTvYjXhNpWJr .image-shape .label,#mermaid-svg-MN0pZTvYjXhNpWJr .icon-shape .label{text-align:center;}#mermaid-svg-MN0pZTvYjXhNpWJr .node.clickable{cursor:pointer;}#mermaid-svg-MN0pZTvYjXhNpWJr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MN0pZTvYjXhNpWJr .arrowheadPath{fill:#333333;}#mermaid-svg-MN0pZTvYjXhNpWJr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MN0pZTvYjXhNpWJr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MN0pZTvYjXhNpWJr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MN0pZTvYjXhNpWJr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MN0pZTvYjXhNpWJr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MN0pZTvYjXhNpWJr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MN0pZTvYjXhNpWJr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MN0pZTvYjXhNpWJr .cluster text{fill:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr .cluster span{color:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MN0pZTvYjXhNpWJr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MN0pZTvYjXhNpWJr rect.text{fill:none;stroke-width:0;}#mermaid-svg-MN0pZTvYjXhNpWJr .icon-shape,#mermaid-svg-MN0pZTvYjXhNpWJr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MN0pZTvYjXhNpWJr .icon-shape p,#mermaid-svg-MN0pZTvYjXhNpWJr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MN0pZTvYjXhNpWJr .icon-shape .label rect,#mermaid-svg-MN0pZTvYjXhNpWJr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MN0pZTvYjXhNpWJr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MN0pZTvYjXhNpWJr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MN0pZTvYjXhNpWJr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} tsc 编译
Node.js / 浏览器
TypeScript 源码
xxx.ts
JavaScript
xxx.js
运行
类型检查在编译期完成
错误提前暴露
1.3 为什么 Java 程序员学 TS 有天然优势?
这可能是你最容易松一口气的一点:
| 维度 | Java | TypeScript |
|---|---|---|
| 类型系统 | 静态类型 | 静态类型(渐进式) |
| 类型检查时机 | 编译期 | 编译期 |
| 变量声明 | int x = 10 |
let x: number = 10 |
| 类型位置 | 类型在前 | 类型在后(x: number) |
两者都是「编译期检查类型」的静态类型语言 ,思路一脉相承。唯一的差别是:TS 是「渐进式」的------你可以先不写类型(相当于 JS),再慢慢加类型。而 Java 是强制的。
一句话 :TS 就是「给 JS 装上了类型安全」。Java 程序员学它,等于把已经会的「静态类型思维」平移到一个新语法上,而不是从零学一门全新的范式。这是你最大的优势,别浪费。
二、10 分钟补上最小必要的 JS 基础(Java 对照)
既然 TS 是 JS 的超集,你得先懂点 JS。好在不难,用 Java 对照着看。
2.1 变量:let 和 const
typescript
let age = 18; // let:可变变量(类比 Java 的普通局部变量)
age = 19; // ✅ 可以改
const name = "小明"; // const:常量,赋值后不能改(类比 Java 的 final)
// name = "小红"; // ❌ 报错
// var 是旧写法,有各种坑,别用!
2.2 函数:function 与箭头函数
typescript
// ① 传统函数声明
function add(a: number, b: number): number {
return a + b;
}
// ② 箭头函数(类比 Java 的 lambda)
const add2 = (a: number, b: number): number => a + b;
箭头函数 => 就是 Java 里的 ->,(参数) => 返回值。
2.3 对象字面量:没有 class 也能造对象
JS 里可以不写 class,直接用 {} 造一个对象(你可以把它理解成一个「加强版的 Map」):
typescript
const user = {
name: "小明",
age: 18,
greet() {
return "你好,我是" + this.name;
}
};
console.log(user.name); // 小明
console.log(user.greet()); // 你好,我是小明
2.4 数组与 undefined / null
typescript
const nums: number[] = [1, 2, 3];
nums.push(4); // 加元素
// undefined:变量声明了但没赋值
// null:显式地表示「空」
let a; // a 是 undefined
let b = null; // b 是 null
2.5 Java 概念 → JS/TS 对照表
| Java | TypeScript | 说明 |
|---|---|---|
int x = 10 |
let x: number = 10 |
类型写在变量名后 |
final int x |
const x = 10 |
常量 |
(x) -> x*2 |
(x) => x*2 |
箭头函数 |
Map / POJO |
对象字面量 {} |
键值对的集合 |
List<T> |
T[] |
数组 |
null |
undefined / null |
两个「空」概念 |
一句话 :JS 的变量用
let/const,函数可以写成箭头=>,对象可以直接用{}字面量造出来。这些是 TS 的「地基」,先把它们混个脸熟,后面就顺了。
三、搭建第一个 TS 项目(实战起点)
动手环节,先搭一个能跑的最小项目。
3.1 安装环境
需要装两样东西:
bash
# ① Node.js(提供 JS 运行时 + npm 包管理器)
# ② TypeScript 编译器
npm install -g typescript
装好后,用 tsc 命令验证:tsc --version。
3.2 初始化项目
bash
mkdir todo-app
cd todo-app
npm init -y # 生成 package.json(类比 pom.xml)
tsc --init # 生成 tsconfig.json(TS 的编译配置)
两个关键文件:
| 文件 | 作用 | Java 类比 |
|---|---|---|
package.json |
项目信息 + 依赖 | pom.xml / build.gradle |
tsconfig.json |
TS 编译配置 | 编译选项 |
3.3 写第一个 TS 文件并运行
新建 index.ts:
typescript
const message: string = "Hello, TypeScript!";
console.log(message);
编译并运行:
bash
tsc index.ts # 编译:生成 index.js
node index.js # 运行:打印 Hello, TypeScript!
#mermaid-svg-cNoA7fticiXgwYwI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cNoA7fticiXgwYwI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cNoA7fticiXgwYwI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cNoA7fticiXgwYwI .error-icon{fill:#552222;}#mermaid-svg-cNoA7fticiXgwYwI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cNoA7fticiXgwYwI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cNoA7fticiXgwYwI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cNoA7fticiXgwYwI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cNoA7fticiXgwYwI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cNoA7fticiXgwYwI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cNoA7fticiXgwYwI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cNoA7fticiXgwYwI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cNoA7fticiXgwYwI .marker.cross{stroke:#333333;}#mermaid-svg-cNoA7fticiXgwYwI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cNoA7fticiXgwYwI p{margin:0;}#mermaid-svg-cNoA7fticiXgwYwI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cNoA7fticiXgwYwI .cluster-label text{fill:#333;}#mermaid-svg-cNoA7fticiXgwYwI .cluster-label span{color:#333;}#mermaid-svg-cNoA7fticiXgwYwI .cluster-label span p{background-color:transparent;}#mermaid-svg-cNoA7fticiXgwYwI .label text,#mermaid-svg-cNoA7fticiXgwYwI span{fill:#333;color:#333;}#mermaid-svg-cNoA7fticiXgwYwI .node rect,#mermaid-svg-cNoA7fticiXgwYwI .node circle,#mermaid-svg-cNoA7fticiXgwYwI .node ellipse,#mermaid-svg-cNoA7fticiXgwYwI .node polygon,#mermaid-svg-cNoA7fticiXgwYwI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cNoA7fticiXgwYwI .rough-node .label text,#mermaid-svg-cNoA7fticiXgwYwI .node .label text,#mermaid-svg-cNoA7fticiXgwYwI .image-shape .label,#mermaid-svg-cNoA7fticiXgwYwI .icon-shape .label{text-anchor:middle;}#mermaid-svg-cNoA7fticiXgwYwI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cNoA7fticiXgwYwI .rough-node .label,#mermaid-svg-cNoA7fticiXgwYwI .node .label,#mermaid-svg-cNoA7fticiXgwYwI .image-shape .label,#mermaid-svg-cNoA7fticiXgwYwI .icon-shape .label{text-align:center;}#mermaid-svg-cNoA7fticiXgwYwI .node.clickable{cursor:pointer;}#mermaid-svg-cNoA7fticiXgwYwI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cNoA7fticiXgwYwI .arrowheadPath{fill:#333333;}#mermaid-svg-cNoA7fticiXgwYwI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cNoA7fticiXgwYwI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cNoA7fticiXgwYwI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cNoA7fticiXgwYwI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cNoA7fticiXgwYwI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cNoA7fticiXgwYwI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cNoA7fticiXgwYwI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cNoA7fticiXgwYwI .cluster text{fill:#333;}#mermaid-svg-cNoA7fticiXgwYwI .cluster span{color:#333;}#mermaid-svg-cNoA7fticiXgwYwI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cNoA7fticiXgwYwI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cNoA7fticiXgwYwI rect.text{fill:none;stroke-width:0;}#mermaid-svg-cNoA7fticiXgwYwI .icon-shape,#mermaid-svg-cNoA7fticiXgwYwI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cNoA7fticiXgwYwI .icon-shape p,#mermaid-svg-cNoA7fticiXgwYwI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cNoA7fticiXgwYwI .icon-shape .label rect,#mermaid-svg-cNoA7fticiXgwYwI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cNoA7fticiXgwYwI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cNoA7fticiXgwYwI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cNoA7fticiXgwYwI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 写 index.ts
tsc 编译
生成 index.js
node 运行
一句话 :TS 项目 = Node.js +
npm管理依赖 +tsc编译 +tsconfig.json配置。和 Java 的「编译 → 运行」流程一模一样,只是编译器从javac换成了tsc。
四、TS 的类型系统(用 Java 对照,重点)
现在进入 TS 的核心------类型。你会发现很多概念你都见过。
4.1 基础类型
typescript
let age: number = 18; // 数字(不区分 int/long/double,统一 number)
let name: string = "小明"; // 字符串
let ok: boolean = true; // 布尔
let nums: number[] = [1, 2, 3]; // 数组
let tuple: [string, number] = ["小明", 18]; // 元组:定长定类型
4.2 类型标注:类型写在「变量名后」
这是 TS 和 Java 语法上最明显的差别------类型在冒号后面:
java
// Java:类型在前
int age = 18;
String name = "小明";
typescript
// TypeScript:类型在后
let age: number = 18;
let name: string = "小明";
🎯 小技巧:大多数情况下,TS 能自动推断 类型,你甚至可以不写
: number,它自己知道let age = 18的age是number。这个叫「类型推断」,比 Java 省事多了。
4.3 any 和 unknown:两个「万能类型」
这是 Java 里没有的概念,但很重要:
typescript
// any:完全关掉类型检查,想怎么用都行(危险,尽量少用)
let x: any = 10;
x = "hello"; // ✅ 不报错
x.foo(); // ✅ 也不报错(但运行时可能崩)
// unknown:也是「任意类型」,但更安全------用之前必须先收窄类型
let y: unknown = 10;
// y.foo(); // ❌ 报错:unknown 不能直接用
if (typeof y === "number") {
y.toFixed(2); // ✅ 先判断类型,才能用
}
| 类型 | 含义 | 安全性 | 类比 |
|---|---|---|---|
any |
任意类型,关闭检查 | 危险 | Java 的 Object(但更没约束) |
unknown |
任意类型,用前需收窄 | 安全 | Java 的 Object + 强转检查 |
4.4 联合类型:TS 的招牌(Java 没有)
TS 最实用的特性之一------一个变量可以是多种类型之一 ,用 | 连接:
typescript
let id: string | number; // id 可以是字符串,也可以是数字
id = "abc"; // ✅
id = 123; // ✅
// id = true; // ❌ 报错
4.5 字面量类型与 type 别名
typescript
// 字面量类型:值只能是这几个
type Status = "todo" | "doing" | "done";
let status: Status = "todo"; // ✅
// status = "xxx"; // ❌ 只能是那三个之一
// type 别名:给类型起个名字
type UserId = string;
let id: UserId = "u123";
一句话 :TS 的类型系统 = 「你熟悉的 Java 静态类型」+ 几个新朋友(
any/unknown、联合类型|、字面量类型、type别名)。其中联合类型是 TS 最有辨识度的特性,务必拿下。
五、interface 与 type:给「对象」定型(重点)
Java 里你用 class 定义「对象长什么样」,TS 里你更常用 interface 或 type。
5.1 interface:定义对象的「形状」
typescript
interface User {
name: string;
age: number;
email?: string; // 可选属性(可以没有)
readonly id: string; // 只读(赋值后不能改)
}
const u: User = {
name: "小明",
age: 18,
id: "u001",
// email 可以不写,因为是可选的
};
注意几个和 Java 接口的差别:
| 特性 | Java interface | TS interface |
|---|---|---|
| 定义内容 | 方法签名 | 属性 + 方法 |
| 实现方式 | implements 显式 |
结构匹配(自动) |
| 可选属性 | 无 | ? 标记 |
| 只读 | final 字段 |
readonly |
5.2 type 类型别名
typescript
type Point = {
x: number;
y: number;
};
const p: Point = { x: 1, y: 2 };
5.3 interface vs type 怎么选?
| 场景 | 建议 |
|---|---|
| 定义对象的「形状」 | 优先 interface |
| 联合类型、元组、简单别名 | 用 type |
| 需要「扩展/继承」 | 优先 interface(可用 extends) |
typescript
// interface 可以继承
interface Animal { name: string; }
interface Dog extends Animal { bark(): void; }
一句话 :TS 里「定义一个数据长什么样」主要靠
interface。它比 Java 接口更「亲民」------直接描述「对象有哪些字段」,还能用?标可选、readonly标只读。给你的数据模型定型,是 TS 项目的第一步。
六、函数与类:回到你熟悉的领域
终于讲到 class 了------这一节你会感觉「回家了」。
6.1 函数类型标注
typescript
// 参数类型 + 返回类型都标上
function greet(name: string): string {
return "你好," + name;
}
// 可选参数(?)和默认参数
function hello(name: string, title?: string, age: number = 18): string {
return `${name} ${age} 岁`;
}
6.2 class:你熟悉的 class 回来了
typescript
class Animal {
private name: string; // private:只在类内可见
constructor(name: string) {
this.name = name;
}
public speak(): string { // public:默认就是 public
return this.name;
}
}
const a = new Animal("小狗");
console.log(a.speak());
访问修饰符和 Java 基本一样:
| 修饰符 | 含义 |
|---|---|
public |
公开(默认) |
private |
私有,只在类内可见 |
protected |
类内 + 子类可见 |
6.3 抽象类与 implements
typescript
abstract class Shape {
abstract area(): number; // 抽象方法
}
interface Printable {
print(): void;
}
// 实现接口(终于见到 implements 了)
class Circle extends Shape implements Printable {
constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
print(): void { console.log(this.area()); }
}
一句话 :TS 的
class和 Java 的class高度相似------构造器、private/protected、extends、implements、抽象类都在。这一节你几乎不用学新东西,只是语法细节略有不同。
七、泛型:Java 泛型的老朋友
如果你懂 Java 的 List<T>、Map<K,V>,那 TS 泛型对你就是「换了个写法」。
7.1 泛型函数
java
// Java
public static <T> T identity(T x) { return x; }
typescript
// TypeScript
function identity<T>(x: T): T {
return x;
}
const a = identity<string>("hello"); // a 是 string
const b = identity<number>(42); // b 是 number
7.2 泛型接口
typescript
interface Repository<T> {
getAll(): T[];
getById(id: number): T | undefined;
add(item: T): void;
}
7.3 泛型约束
typescript
// T 必须「长得像」有 id 字段的对象
function findById<T extends { id: number }>(list: T[], id: number): T | undefined {
return list.find(item => item.id === id);
}
一句话 :TS 泛型 = Java 泛型的「孪生兄弟」。
<T>的位置、extends约束、泛型接口,思路完全一致。你只需要把List<T>换成T[],把Map<K,V>换成Map<K,V>(这个甚至同名)。
八、类型进阶:联合、收窄、工具类型(克制版)
这些是 TS 的「进阶」能力,日常用得不少,但点到为止。
8.1 类型收窄(Narrowing)
联合类型虽好用,但**用之前得先「收窄」**成具体类型:
typescript
function print(value: string | number) {
if (typeof value === "string") { // 收窄成 string
console.log(value.toUpperCase());
} else { // 这里 value 是 number
console.log(value.toFixed(2));
}
}
判断手段有:typeof(判断基本类型)、instanceof(判断类)、in(判断属性存在)。
8.2 类型断言 as
把类型「手动指定」为某个类型(类比 Java 的强转,但更安全):
typescript
const value: unknown = "hello";
const str = value as string; // 断言成 string
console.log(str.length);
8.3 常用工具类型
TS 内置了一批「类型工具」,帮你基于已有类型造新类型:
typescript
interface Todo {
id: number;
title: string;
done: boolean;
}
type TodoInput = Partial<Todo>; // 所有字段变可选
type IdOnly = Pick<Todo, "id">; // 只取 id 字段
type NoId = Omit<Todo, "id">; // 去掉 id 字段
| 工具类型 | 作用 |
|---|---|
Partial<T> |
所有字段变可选 |
Pick<T, K> |
挑选部分字段 |
Omit<T, K> |
去掉部分字段 |
Record<K, V> |
造「键 → 值」映射类型 |
一句话 :联合类型 + 类型收窄,是 TS 处理「一个变量多种类型」的完整套路;
as断言和工具类型(Partial/Pick/Omit)则是日常高频的「类型体操」。先认识它们,用到时再回来查即可。
九、项目实战:从零写一个 TODO 应用(主线收口)
前面学的所有东西,现在串成一个完整项目。我们要写一个命令行 TODO 应用------能增删改查任务。
9.1 项目结构
#mermaid-svg-Bjgq1vAdY5VuWFMy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bjgq1vAdY5VuWFMy .error-icon{fill:#552222;}#mermaid-svg-Bjgq1vAdY5VuWFMy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bjgq1vAdY5VuWFMy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .marker.cross{stroke:#333333;}#mermaid-svg-Bjgq1vAdY5VuWFMy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bjgq1vAdY5VuWFMy p{margin:0;}#mermaid-svg-Bjgq1vAdY5VuWFMy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .cluster-label text{fill:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .cluster-label span{color:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .cluster-label span p{background-color:transparent;}#mermaid-svg-Bjgq1vAdY5VuWFMy .label text,#mermaid-svg-Bjgq1vAdY5VuWFMy span{fill:#333;color:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .node rect,#mermaid-svg-Bjgq1vAdY5VuWFMy .node circle,#mermaid-svg-Bjgq1vAdY5VuWFMy .node ellipse,#mermaid-svg-Bjgq1vAdY5VuWFMy .node polygon,#mermaid-svg-Bjgq1vAdY5VuWFMy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .rough-node .label text,#mermaid-svg-Bjgq1vAdY5VuWFMy .node .label text,#mermaid-svg-Bjgq1vAdY5VuWFMy .image-shape .label,#mermaid-svg-Bjgq1vAdY5VuWFMy .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bjgq1vAdY5VuWFMy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .rough-node .label,#mermaid-svg-Bjgq1vAdY5VuWFMy .node .label,#mermaid-svg-Bjgq1vAdY5VuWFMy .image-shape .label,#mermaid-svg-Bjgq1vAdY5VuWFMy .icon-shape .label{text-align:center;}#mermaid-svg-Bjgq1vAdY5VuWFMy .node.clickable{cursor:pointer;}#mermaid-svg-Bjgq1vAdY5VuWFMy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .arrowheadPath{fill:#333333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bjgq1vAdY5VuWFMy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bjgq1vAdY5VuWFMy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bjgq1vAdY5VuWFMy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bjgq1vAdY5VuWFMy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .cluster text{fill:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy .cluster span{color:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bjgq1vAdY5VuWFMy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bjgq1vAdY5VuWFMy rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bjgq1vAdY5VuWFMy .icon-shape,#mermaid-svg-Bjgq1vAdY5VuWFMy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bjgq1vAdY5VuWFMy .icon-shape p,#mermaid-svg-Bjgq1vAdY5VuWFMy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bjgq1vAdY5VuWFMy .icon-shape .label rect,#mermaid-svg-Bjgq1vAdY5VuWFMy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bjgq1vAdY5VuWFMy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bjgq1vAdY5VuWFMy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bjgq1vAdY5VuWFMy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} todo-app
index.ts(入口 + 演示)
todo.ts(数据模型 + 存储层)
tsconfig.json(编译配置)
package.json(项目信息)
9.2 第一步:定义数据模型(interface)
用 interface 定义 Todo 的形状:
typescript
// todo.ts
// 任务状态:用字面量联合类型,限定只能是这三个值
export type TodoStatus = "todo" | "doing" | "done";
// 任务数据模型
export interface Todo {
id: number;
title: string;
status: TodoStatus;
}
// 新建任务时不需要 id(id 由存储层自动生成)
export type CreateTodo = Omit<Todo, "id">;
这里已经用到了前面学的 interface、联合类型、type、Omit 工具类型。
9.3 第二步:写一个泛型存储层(泛型)
用泛型写一个通用的「仓库」,能存任意类型的数据:
typescript
// todo.ts
// 泛型存储层:T 可以是任何类型
export class Repository<T extends { id: number }> {
private items: T[] = [];
getAll(): T[] {
return this.items;
}
getById(id: number): T | undefined {
return this.items.find(item => item.id === id);
}
add(item: T): void {
this.items.push(item);
}
remove(id: number): void {
this.items = this.items.filter(item => item.id !== id);
}
update(id: number, patch: Partial<T>): T | undefined {
const item = this.getById(id);
if (item) {
Object.assign(item, patch); // 合并修改
}
return item;
}
}
这里用到了 class、泛型 <T>、泛型约束 extends { id: number }、联合类型 T | undefined、Partial 工具类型。
9.4 第三步:写 TODO 服务(组合 + 类型收窄)
typescript
// todo.ts
let nextId = 1;
export class TodoService {
private repo = new Repository<Todo>();
// 创建任务:自动生成 id
create(input: CreateTodo): Todo {
const todo: Todo = { id: nextId++, ...input };
this.repo.add(todo);
return todo;
}
// 查询任务:返回 undefined 表示没找到
find(id: number): Todo | undefined {
return this.repo.getById(id);
}
// 标记为完成
complete(id: number): void {
this.repo.update(id, { status: "done" });
}
// 列出指定状态的任务
listByStatus(status: TodoStatus): Todo[] {
return this.repo.getAll().filter(t => t.status === status);
}
}
9.5 第四步:入口,跑起来(类型收窄)
typescript
// index.ts
import { TodoService } from "./todo";
function printTodo(todo: ReturnType<TodoService["find"]>): void {
// 类型收窄:todo 可能是 undefined,先判断
if (todo) {
console.log(`[${todo.id}] ${todo.title} (${todo.status})`);
} else {
console.log("任务不存在");
}
}
const service = new TodoService();
// 创建两个任务
service.create({ title: "学 TypeScript", status: "doing" });
service.create({ title: "写博客", status: "todo" });
// 完成任务 1
service.complete(1);
// 查询
const t = service.find(1);
printTodo(t); // [1] 学 TypeScript (done)
// 列出所有「未完成」的任务
const todos = service.listByStatus("todo");
console.log("未完成任务:", todos.length);
9.6 编译运行
bash
tsc index.ts todo.ts # 编译
node index.js # 运行
Repository TodoService index.ts Repository TodoService index.ts #mermaid-svg-AscoL7yF0P5vMmt1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AscoL7yF0P5vMmt1 .error-icon{fill:#552222;}#mermaid-svg-AscoL7yF0P5vMmt1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AscoL7yF0P5vMmt1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AscoL7yF0P5vMmt1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AscoL7yF0P5vMmt1 .marker.cross{stroke:#333333;}#mermaid-svg-AscoL7yF0P5vMmt1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AscoL7yF0P5vMmt1 p{margin:0;}#mermaid-svg-AscoL7yF0P5vMmt1 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AscoL7yF0P5vMmt1 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AscoL7yF0P5vMmt1 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AscoL7yF0P5vMmt1 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AscoL7yF0P5vMmt1 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AscoL7yF0P5vMmt1 .sequenceNumber{fill:white;}#mermaid-svg-AscoL7yF0P5vMmt1 #sequencenumber{fill:#333;}#mermaid-svg-AscoL7yF0P5vMmt1 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AscoL7yF0P5vMmt1 .messageText{fill:#333;stroke:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AscoL7yF0P5vMmt1 .labelText,#mermaid-svg-AscoL7yF0P5vMmt1 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .loopText,#mermaid-svg-AscoL7yF0P5vMmt1 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AscoL7yF0P5vMmt1 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AscoL7yF0P5vMmt1 .noteText,#mermaid-svg-AscoL7yF0P5vMmt1 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AscoL7yF0P5vMmt1 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AscoL7yF0P5vMmt1 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AscoL7yF0P5vMmt1 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AscoL7yF0P5vMmt1 .actorPopupMenu{position:absolute;}#mermaid-svg-AscoL7yF0P5vMmt1 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AscoL7yF0P5vMmt1 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AscoL7yF0P5vMmt1 .actor-man circle,#mermaid-svg-AscoL7yF0P5vMmt1 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AscoL7yF0P5vMmt1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} create({title, status})生成 id,组装 Todoadd(todo)complete(1)update(1, {status:"done"})find(1)getById(1)返回 Todo | undefined
一句话 :这个 30 行的 TODO 项目,把前面所有概念都串起来了------
interface定型、联合类型约束状态、泛型Repository复用存储逻辑、Partial/Omit造衍生类型、类型收窄处理undefined。这就是 TS 项目最真实的模样:先定类型,再写逻辑,编译器帮你兜底。