后端开发初学TypeScript

一个只学过 Java 的人,第一次打开前端代码,大概会是这个表情:

  • letconstvar 到底用哪个?
  • 为什么函数可以写成 => 这种箭头?
  • undefinednull 有啥区别?
  • 最要命的:为什么一个变量,一会儿是数字、一会儿又变成字符串了? 类型呢?

你可能会慌:「这语言也太随便了吧。」

但先别急着否定。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 = 18agenumber。这个叫「类型推断」,比 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 里你更常用 interfacetype

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/protectedextendsimplements、抽象类都在。这一节你几乎不用学新东西,只是语法细节略有不同。


七、泛型: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、联合类型、typeOmit 工具类型

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 | undefinedPartial 工具类型

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 项目最真实的模样:先定类型,再写逻辑,编译器帮你兜底。


相关推荐
90后的晨仔15 分钟前
Puppeteer 与 Playwright 深度实战指南:从零到精通,全面提升开发效率
前端
sunphp开发者22 分钟前
阿里云CDN加速配置问题
前端·阿里云·云计算
小小数媒成员29 分钟前
存储器层级结构
java·服务器·前端
单线程121382 小时前
从案例分析 Vue3 Tokenizer 源码
前端·javascript·vue.js
明月_清风2 小时前
🖥️ Electron 三进程 Host 实战:从架构设计到生产落地的完整指南
前端·electron·客户端
GIS数据转换器2 小时前
智慧林草“一张图“平台
java·大数据·服务器·前端·javascript·数据库·人工智能
2401_885885042 小时前
国际语音php接口代码示例:PHP使用cURL快速调用语音发送API
android·开发语言·前端·人工智能·python·php·语音识别
leoZ2312 小时前
AI+前端提效-08 AI自动化文档:前端组件、接口、项目文档自动生成
前端·人工智能·深度学习·神经网络·目标检测·自然语言处理·自动化
lilian2332 小时前
HarmonyOS 7 新特性(五)|ContainerReader 容器断点与自适应布局
前端·华为·harmonyos