教学教程 —— 从零学会写汉语 TypeScript
⬇ ChineseTypeScript 教学教程 —— 下载 Word 版(.docx) 适合打印、发邮件、或发给不习惯看网页的人。内容与本页相同。
这本教程教你三件事:汉语关键字怎么用(每个都给出对应的英文)、 原本那套符号怎么用(
{}();=>?.??… 一个没换)、 基础标准库怎么用(这一条有个重要前提,见下)。⚠ 与 Chinese++ 的一个重大差别:Chinese++ 连标准库都做了汉化层 (
#包含 <向量>、标准::向量<整数>)。ChineseTypeScript 只汉化了语言关键字,console/Array/Map/JSON这些运行库的名字没有中文别名, 一律照写英文(英文永远可用)。本文件是生成物,不要手改。 由
tools\make_tutorial_chts.ps1把教程\例子\*.ts的真源码与「期望输出」切进教程\教程模板.md生成。tests-chts\judge_tutorial.ps1会真编译真运行每个例子并核对输出, 同时核对本文件与例子是否逐字一致 —— 所以教程里的代码必然能跑。
怎么用这本教程
- 先读「第 0 课」把编译命令跑通。
- 每课都有一份能直接编译运行的例子,放在
教程\例子\。 - 例子开头的注释里写了它自己的期望输出 —— 你跑出来的应当一模一样。
- 汉语写法和英文写法可以混着写,同一行里混都行。
第 0 课 准备:编、跑
编译器:E:\cts-build\chtsc.exe(用 tools\build_chtsc.ps1 构建;名字见「命令名」一节)。
chtsc 你好.ts --outDir 输出 --target es2020 --module commonjs
node 输出\你好.js一个最小程序长这样(第 1 课细讲):
函数 打招呼(): 空型 {
console.log("你好,世界");
}
打招呼();第 1 课 程序骨架
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
函数 | function | 定义函数 |
返回 | return | 从函数返回 |
空型 | void | 「什么也不返回」 |
数字 | number | 数字类型(TypeScript 的数字都是浮点) |
本课符号
| 符号 | 名称 | 用法 |
|---|---|---|
{ } | 花括号 | 包住一段代码(函数体、分支体) |
( ) | 圆括号 | 参数表 / 调用函数 |
: | 冒号 | 类型标注:甲: 数字 读作「甲,类型是数字」 |
; | 分号 | 语句末尾。TypeScript 里可以省,但建议写(本教程一律写) |
// | 行注释 | 到这里行尾都不算代码 |
" " | 双引号 | 字符串 |
+ | 加号 | 数字相加;字符串拼接也用它 |
例子的真源码
// 第 1 课:程序骨架
//
// 关键字(英文对照):函数(function) 返回(return) 空型(void) 数字(number)
// 符号:{ } 包代码块、( ) 参数表、; 语句结束、: 类型标注、// 注释、"" 字符串
//
// 期望输出:
// 你好,世界
// 汉语编程
// 两数之和=7
函数 相加(甲: 数字, 乙: 数字): 数字 {
返回 甲 + 乙; // 返回 = return
}
函数 打招呼(): 空型 { // 空型 = void:不返回东西
console.log("汉语编程");
}
console.log("你好,世界");
打招呼();
console.log("两数之和=" + 相加(3, 4));跑出来的结果(与例子里的「期望输出」一致):
你好,世界
汉语编程
两数之和=7讲解
console.log(...)是打印。console是运行库的名字,没有中文别名。- 函数签名读法:
函数 相加(甲: 数字, 乙: 数字): 数字—— 名字、参数(每个参数名字: 类型)、最后是返回类型。 - TypeScript 不像 C++ 那样要求有个
main;文件从上往下执行。
练习
- 写一个
函数 相乘(甲: 数字, 乙: 数字): 数字,打印相乘(3, 4)。 - 把
函数写成英文function,确认结果一样(中英可混写)。
第 2 课 变量与类型
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
常量 | const | 之后不许重新赋值(首选) |
局部变量 | let | 可以重新赋值,作用域限制在块里 |
变量 | var | 老写法,作用域是整个函数(新代码别用) |
接口 | interface | 描述一个对象长什么样 |
类型别名 | type | 给类型起名字 |
数字 字符串 布尔 | number string boolean | 基本类型 |
真值 / 假值 | true / false | 布尔字面量 |
空值 | null | 「空」 —— 值和类型两种位置都能用 |
未定义 | undefined | 「未定义」 —— 当类型可以,当值要写英文(附录 C) |
任意 未知 永不 | any unknown never | 特殊类型 |
本课符号
| 符号 | 用法 |
|---|---|
: | 类型标注:常量 年龄: 数字 = 18; |
= | 赋值 |
[] | 数组类型 数字[];取下标 表[0](从 0 开始) |
| | 联合类型:字符串 | 未定义 读作「字符串或未定义」 |
? | 可选属性:名字?: 字符串 |
[ ] | 元组类型:[字符串, 数字] —— 按位置标类型 |
{ } | 对象字面量 |
例子的真源码
// 第 2 课:变量与类型
//
// 关键字(英文对照):
// 常量(const) 局部变量(let) 变量(var) 类型别名(type) 接口(interface)
// 数字(number) 字符串(string) 布尔(boolean) 真值(true) 假值(false)
// 空值(null) 未定义(undefined) 任意(any) 未知(unknown) 永不(never)
// 符号:: 类型标注、= 赋值、[] 数组、| 联合类型、{ } 对象字面量
//
// 期望输出:
// 年龄=18
// 身高=1.75
// 名字=小明
// 已成年=true
// 表长度=3
// 元组=甲,1
// 对象=小红,20
// 联合类型=已启用
// 空值类型=object
// 未定义类型=undefined
//
// 注:`空值`(null) 与 `未定义`(undefined) 当【值】和当【类型】都可以。
// 要记住的是另一件事:属性名与成员名里的汉语词是运行时的真实属性名,
// 不会被翻译(`{ 未定义: 1 }` 的属性名就叫「未定义」,见第 9 课与附录 C)。
常量 年龄: 数字 = 18; // 常量 = const
局部变量 身高 = 1.75; // 局部变量 = let;类型由右边的值推出来
变量 名字: 字符串 = "小明"; // 变量 = var(老写法,新代码请用 常量/局部变量)
常量 已成年: 布尔 = 真值; // 真值 = true
常量 表: 数字[] = [1, 2, 3]; // 数字[] = 「数字的数组」
常量 元组: [字符串, 数字] = ["甲", 1]; // 元组:类型按位置写
接口 人 { 名字: 字符串; 年龄: 数字 } // 接口 = interface
常量 对象: 人 = { 名字: "小红", 年龄: 20 };
类型别名 状态 = "启用" | "停用"; // 类型别名 = type;| 是联合类型
常量 当前: 状态 = "启用";
常量 空的 = 空值; // 空值 = null(当值用)
常量 空着 = 未定义; // 未定义 = undefined(值位置同样可用)
console.log("年龄=" + 年龄);
console.log("身高=" + 身高);
console.log("名字=" + 名字);
console.log("已成年=" + 已成年);
console.log("表长度=" + 表.length); // .length 是 JS 内建的成员,英文
console.log("元组=" + 元组[0] + "," + 元组[1]);
console.log("对象=" + 对象.名字 + "," + 对象.年龄);
console.log("联合类型=" + (当前 === "启用" ? "已启用" : "已停用"));
console.log("空值类型=" + typeof 空的);
console.log("未定义类型=" + typeof 空着);跑出来的结果:
年龄=18
身高=1.75
名字=小明
已成年=true
表长度=3
元组=甲,1
对象=小红,20
联合类型=已启用
空值类型=object
未定义类型=undefined讲解
- 优先用
常量:能不变就不变。需要重新赋值才用局部变量。 局部变量 身高 = 1.75;没写类型 —— TypeScript 自己推出来了(推断)。接口只描述形状,不生成代码;类型别名用来给联合类型等起名字。.length是 JS 内建的成员,数的是 UTF-16 码元(一个汉字算 1)。
练习
- 定义一个
接口 书 { 书名: 字符串; 页数: 数字 }并创建一个常量。 - 用
类型别名定义一个「数字或字符串」的联合类型并各用一个值。
第 3 课 运算符与符号
本课最重要的一句话:符号一个都没换。 注意与 C++ 的差别 —— C++ 有「运算符替代拼写」(并且/或者/并非), TypeScript 这边没有,&& || ! 照写。
本课符号
| 类别 | 符号 | 说明 |
|---|---|---|
| 算术 | + - * / % | + 也用于字符串拼接 |
| 比较 | === !== == != < > <= >= | 推荐 === / !==(严格相等,不做类型转换) |
| 逻辑 | && || ! | 与或非 |
| 空值合并 | ?? | 左边是 空值/undefined 时才取右边 |
| 可选链 | ?. | 左边是空就整个表达式给 undefined,不报错 |
| 三元 | ? : | 条件 ? 真时 : 假时 |
| 赋值 | = += -= *= /= | 甲 += 1 等价 甲 = 甲 + 1 |
| 自增自减 | ++ -- | |
| 展开/剩余 | ... | [...表甲, 3] 把数组摊开;函数参数里把剩下的收起来 |
| 模板串 | ` 与 ${} | `你好,${名字}` |
| 类型相关 | : | & <> ? ! | 标注、联合、交叉、泛型、可选、非空断言 |
例子的真源码
// 第 3 课:运算符与符号
//
// 本课最重要的一句话:**TypeScript 这边符号一个都没换。**
// 而且它跟 C++ 那边不同 —— C++ 有「运算符替代拼写」(`并且`/`或者`/`并非`),
// TypeScript 这边【没有】,`&&` `||` `!` 照写。
//
// 关键字(英文对照):常量(const) 数字(number) 字符串(string) 布尔(boolean)
// 真值(true) 假值(false) 未定义(undefined) 空值(null) 接口(interface) 类型别名(type)
// 符号:算术 + - * / %、比较 === !== == != < > <= >=、逻辑 && || !、
// 空值合并 ??、可选链 ?.、三元 ? :、展开 ...、模板串 ` 与 ${}
//
// 期望输出:
// 算术=10 4 21 2 1
// 比较=true false true
// 严格相等=true 松散相等=true
// 逻辑=false true false
// 空值合并=默认值
// 可选链=undefined
// 三元=较大
// 展开=[1,2,3,4]
// 模板串=你好,小明!你有2条消息
常量 甲: 数字 = 7; // 显式写 : 数字,免得被推成字面量类型 7
常量 乙: 数字 = 3;
// —— 算术:+ - * / % 全部照写 ——
console.log("算术=" + (甲 + 乙) + " " + (甲 - 乙) + " " + (甲 * 乙) + " " + Math.floor(甲 / 乙) + " " + (甲 % 乙));
// —— 比较:=== 严格相等(推荐),== 松散相等 ——
console.log("比较=" + (甲 > 乙) + " " + (甲 < 乙) + " " + (甲 !== 乙));
console.log("严格相等=" + (甲 === 7) + " 松散相等=" + (甲 == 7));
// —— 逻辑:&& || ! 照写(没有汉语替代拼写)——
console.log("逻辑=" + (真值 && 假值) + " " + (真值 || 假值) + " " + (!真值));
// —— 空值合并 ??:左边是 空值 或 undefined 时才用右边 ——
常量 可能空: 字符串 | 未定义 = 未定义; // 类型位与值位都可以写 未定义
console.log("空值合并=" + (可能空 ?? "默认值"));
// —— 可选链 ?.:左边是 空值/undefined 就整个表达式给 undefined,不报错 ——
接口 配置 { 名字?: 字符串 } // ? 表示「这个属性可以没有」
常量 配置甲: 配置 = {};
console.log("可选链=" + 配置甲.名字?.length);
// —— 三元 ? : ——
console.log("三元=" + (甲 > 乙 ? "较大" : "较小"));
// —— 展开 ...(把数组/对象摊开)——
常量 表甲 = [1, 2];
常量 表乙 = [...表甲, 3, 4];
console.log("展开=[" + 表乙.join(",") + "]");
// —— 模板串:反引号包起来,${} 里可以放任何表达式 ——
常量 名字 = "小明";
常量 条数 = 2;
console.log(`模板串=你好,${名字}!你有${条数}条消息`);跑出来的结果:
算术=10 4 21 2 1
比较=true false true
严格相等=true 松散相等=true
逻辑=false true false
空值合并=默认值
可选链=undefined
三元=较大
展开=[1,2,3,4]
模板串=你好,小明!你有2条消息讲解
===和==的区别:===要求类型也相同,==会做类型转换。 写===能避开一整类古怪的 bug。??与||的区别:||会把0、""也当成「假」,??只在空值/undefined时才用右边 —— 处理「可能没配」的值时用??。- 模板串用反引号(键盘左上角那个),变量写在
${}里。
练习
- 用
??给一个「可能没有的配置项」设默认值。 - 用模板串打印
你好,${名字}!。
第 4 课 分支与循环
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
如果 / 否则 | if / else | 分支 |
选择 | switch | 多分支 |
情形 | case | 选择里的一个分支 |
默认 | default | 选择的兜底分支 |
对于 | for | 计次循环 |
遍历 | of | 对于 (常量 项 遍历 表) —— 遍历元素 |
属于 | in | 「某个键在不在对象里」 |
每当 | while | 先判断再执行 |
执行 | do | 先执行一次再判断(搭配 每当 收尾) |
跳出 / 跳过 | break / continue | 结束整个循环 / 跳过本轮 |
本课符号
| 写法 | 说明 |
|---|---|
如果 (条件) { … } | 条件用 ( ) 包起来 |
如果 … 否则 { … } | 否则 前不写分号 |
选择 (值) { 情形 1: … 跳出; 默认: … } | 情形 后是冒号,每个 情形 末尾要 跳出 |
对于 (初值; 条件; 步进) { … } | 三段用分号隔开 |
执行 { … } 每当 (条件); | do-while:末尾那个 每当 后面必须有 ; |
对于 (常量 项 遍历 容器) { … } | 范围遍历 |
例子的真源码
// 第 4 课:分支与循环
//
// 关键字(英文对照):
// 如果(if) 否则(else) 选择(switch) 情形(case) 默认(default)
// 对于(for) 遍历(of) 属于(in) 每当(while) 执行(do)
// 跳出(break) 跳过(continue) 常量(const) 局部变量(let) 数字(number)
// 符号:() 包条件、{ } 包代码块、; 结束语句、: 跟在 情形/默认 后、++ 自增
//
// 期望输出:
// 及格
// 等级=C
// 累加=10
// 每当两遍后=2
// 执行后=2
// 跳过偶数后=1 3 5
// 遇到5就跳出=1 2 3 4
// 遍历=3 1 2
// 属于=true
常量 分数 = 75;
// —— 如果 / 否则 ——
如果 (分数 >= 60) {
console.log("及格");
} 否则 {
console.log("不及格");
}
// —— 选择 / 情形 / 默认:每个情形末尾要写 跳出,否则会往下穿 ——
常量 档 = Math.floor(分数 / 10);
选择 (档) {
情形 9: console.log("等级=A"); 跳出;
情形 8: console.log("等级=B"); 跳出;
情形 7: console.log("等级=C"); 跳出;
默认: console.log("等级=D");
}
// —— 对于(三段用分号隔开)——
局部变量 累加 = 0;
对于 (局部变量 甲 = 1; 甲 <= 4; 甲++) {
累加 += 甲;
}
console.log("累加=" + 累加);
// —— 每当(先判断再执行)——
局部变量 遍 = 0;
每当 (遍 < 2) {
遍++;
}
console.log("每当两遍后=" + 遍);
// —— 执行…每当(先执行一次再判断)——
局部变量 次 = 0;
执行 {
次++;
} 每当 (次 < 2);
console.log("执行后=" + 次);
// —— 跳过:跳过本轮剩下的 ——
局部变量 跳过输出 = "跳过偶数后=";
对于 (局部变量 甲 = 1; 甲 <= 6; 甲++) {
如果 (甲 % 2 === 0) 跳过;
跳过输出 += 甲 + " ";
}
console.log(跳过输出.trimEnd());
// —— 跳出:直接结束整个循环 ——
局部变量 跳出输出 = "遇到5就跳出=";
对于 (局部变量 甲 = 1; 甲 <= 9; 甲++) {
如果 (甲 === 5) 跳出;
跳出输出 += 甲 + " ";
}
console.log(跳出输出.trimEnd());
// —— 遍历:对于 (常量 项 遍历 容器) ——
常量 表 = [3, 1, 2];
局部变量 遍历输出 = "遍历=";
对于 (常量 项 遍历 表) {
遍历输出 += 项 + " ";
}
console.log(遍历输出.trimEnd());
// —— 属于:某个键在不在对象里 ——
常量 人 = { 名字: "小明", 年龄: 18 };
console.log("属于=" + ("名字" 属于 人));跑出来的结果:
及格
等级=C
累加=10
每当两遍后=2
执行后=2
跳过偶数后=1 3 5
遇到5就跳出=1 2 3 4
遍历=3 1 2
属于=true讲解
选择里的情形是值(数字或字符串),比较用===的语义。- 忘了
跳出会一路穿到下一个情形去(这叫 fallthrough)。 遍历拿的是元素;属于查的是键。这两个别搞混。
练习
- 用
选择把 1~7 打印成「星期一」到「星期日」。 - 用
每当与对于各算一次 1 到 100 的和,对照结果。
第 5 课 函数
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
函数 | function | 定义函数 |
返回 | return | 返回值 |
空型 | void | 不返回值 |
常量 局部变量 | const let | 函数体里的变量 |
任意 未知 | any unknown | 特殊类型 |
本课符号
| 符号 | 用法 |
|---|---|
( ) , | 参数表、分隔参数 |
: 类型 | 参数类型、返回类型 |
=> | 箭头函数:常量 加 = (甲: 数字) => 甲 + 1; |
? | 可选参数:函数 问候(名字?: 字符串) |
= 值 | 默认参数:函数 加上(甲: 数字, 乙: 数字 = 3) |
... | 剩余参数:函数 汇总(...数: 数字[]) |
< > | 泛型参数:函数 取大<甲类型>(…) |
例子的真源码
// 第 5 课:函数
//
// 关键字(英文对照):
// 函数(function) 返回(return) 空型(void) 数字(number) 字符串(string)
// 局部变量(let) 常量(const) 类型别名(type) 任意(any) 未知(unknown)
// 符号:
// ( ) 参数表 / 调用
// : 类型 参数与返回值的类型标注
// => 箭头函数
// ? 可选参数(调用时可以不传)
// = 值 默认参数
// ... 剩余参数(把剩下的都收进一个数组);用在调用处则是展开
// < > 泛型参数
//
// 期望输出:
// 相加=7
// 箭头相加=7
// 默认参数=10
// 可选参数=你好
// 汇总=10
// 泛型取大=9
// 高阶函数=6
// —— 普通函数:参数和返回值都标类型 ——
函数 相加(甲: 数字, 乙: 数字): 数字 {
返回 甲 + 乙;
}
console.log("相加=" + 相加(3, 4));
// —— 箭头函数:=> 读作「变成」——
常量 箭头相加 = (甲: 数字, 乙: 数字): 数字 => 甲 + 乙;
console.log("箭头相加=" + 箭头相加(3, 4));
// —— 默认参数:不传就用 = 后面的值 ——
函数 加上(甲: 数字, 乙: 数字 = 3): 数字 {
返回 甲 + 乙;
}
console.log("默认参数=" + 加上(7));
// —— 可选参数:名字后面写 ? ——
函数 问候(名字?: 字符串): 字符串 {
返回 "你好" + (名字 ? "," + 名字 : "");
}
console.log("可选参数=" + 问候());
// —— 剩余参数:... 把剩下的实参收成一个数组 ——
函数 汇总(...数: 数字[]): 数字 {
局部变量 和 = 0;
对于 (常量 项 遍历 数) {
和 += 项;
}
返回 和;
}
console.log("汇总=" + 汇总(1, 2, 3, 4));
// —— 泛型:一份代码服务多种类型 ——
函数 取大<甲类型>(甲: 甲类型, 乙: 甲类型): 甲类型 {
返回 甲 > 乙 ? 甲 : 乙;
}
console.log("泛型取大=" + 取大(9, 3));
// —— 函数可以当参数传(高阶函数)——
函数 两次(做: (值: 数字) => 数字, 初值: 数字): 数字 {
返回 做(做(初值));
}
console.log("高阶函数=" + 两次((值: 数字) => 值 + 1, 4));跑出来的结果:
相加=7
箭头相加=7
默认参数=10
可选参数=你好
汇总=10
泛型取大=9
高阶函数=6讲解
- 函数是一等公民:可以当参数传、可以当返回值(例子最后一段就是)。
=>读作「变成」:(值) => 值 + 1是「给它一个值,返回它加一」。- 泛型
<甲类型>让一份代码服务多种类型;调用时通常不用写类型实参。
练习
- 写一个带默认参数的函数并调用两次(一次传、一次不传)。
- 写一个把数组里每个数翻倍的高阶函数(参数是一个函数)。
第 6 课 类与接口
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
类型 | class | 定义类(注意:不是「类」,汉语关键字不少于两个字) |
接口 | interface | 描述形状 |
继承 | extends | 继承父类 |
实现 | implements | 声明实现某个接口 |
构造器 | constructor | 构造方法 |
自身 | this | 当前对象 |
父类 | super | 父类 |
重写 | override | 声明「我改写了父类的成员」 |
公开 私有 受保护 | public private protected | 访问控制 |
只读 | readonly | 构造之后不许改 |
静态 | static | 属于类本身 |
取值 / 设值 | get / set | 取值器 / 设值器 |
抽象 | abstract | 抽象类/抽象方法 |
新建 | new | 创建实例 |
本课符号
| 写法 | 说明 |
|---|---|
类型 名字 { … } | 类体 |
公开 名字: 类型; | 属性声明(访问修饰符 + 名字 + 冒号 + 类型) |
继承 父类 实现 接口 | 都用空格,不像 C++ 那样用冒号 |
自身.成员 | 访问自己 |
父类(…) | 调用父类构造器 |
例子的真源码
// 第 6 课:类与接口
//
// 关键字(英文对照):
// 类型(class) 接口(interface) 继承(extends) 实现(implements)
// 构造器(constructor) 自身(this) 重写(override)
// 公开(public) 私有(private) 受保护(protected) 只读(readonly) 静态(static)
// 取值(get) 设值(set) 抽象(abstract) 新建(new) 空型(void) 返回(return)
// 符号:
// { } 类体 ; 类成员之间用分号(或换行)
// : 类型 属性/参数/返回值的类型标注
// => 箭头 方法也可以用箭头写
// 继承 / 实现 都用【空格】,不像 C++ 那样用冒号
//
// 期望输出:
// 名字=小猫 叫声=喵
// 面积=9
// 只读号数=7
// 取值器=10
// 静态计数=1
// 抽象实现面积=9
// 继承来的=我是小猫
// —— 接口:描述「长什么样」——
接口 会叫 {
叫(): 字符串;
}
// —— 类型(class)——
类型 动物 {
公开 名字: 字符串; // 公开 = public(不写也是公开)
私有 秘密 = "藏起来"; // 私有 = private:外面访问不到
受保护 家族 = "动物界"; // 受保护 = protected:子类能访问
只读 号数: 数字 = 7; // 只读 = readonly:构造后不许改
静态 计数 = 0; // 静态 = static:属于类型本身
构造器(名字: 字符串) { // 构造器 = constructor
自身.名字 = 名字; // 自身 = this
}
自我介绍(): 字符串 {
返回 "我是" + 自身.名字;
}
}
// —— 继承 ——
类型 猫 继承 动物 实现 会叫 { // 继承 = extends;实现 = implements
构造器() {
父类("小猫"); // 父类 = super:调用父类构造器
}
叫(): 字符串 {
返回 "喵";
}
}
// —— 抽象类:自己不能直接 new ——
抽象 类型 形状 {
抽象 面积(): 数字; // 抽象方法:子类必须实现
}
类型 正方形 继承 形状 {
边: 数字 = 3;
面积(): 数字 {
返回 自身.边 * 自身.边;
}
}
// —— 取值器 / 设值器 ——
类型 带取值器 {
私有 值 = 5;
取值 十倍(): 数字 { // 取值 = get
返回 自身.值 * 2;
}
设值 十倍(新值: 数字) { // 设值 = set
自身.值 = 新值 / 2;
}
}
常量 猫甲 = 新建 猫(); // 新建 = new
console.log("名字=" + 猫甲.名字 + " 叫声=" + 猫甲.叫());
常量 方 = 新建 正方形();
console.log("面积=" + 方.面积());
console.log("只读号数=" + 猫甲.号数);
常量 取值器 = 新建 带取值器();
console.log("取值器=" + 取值器.十倍);
动物.计数 = 1;
console.log("静态计数=" + 动物.计数);
常量 形: 形状 = 方;
console.log("抽象实现面积=" + 形.面积());
console.log("继承来的=" + 猫甲.自我介绍());跑出来的结果:
名字=小猫 叫声=喵
面积=9
只读号数=7
取值器=10
静态计数=1
抽象实现面积=9
继承来的=我是小猫讲解
类型与 C++ 的class一样,但 TypeScript 里extends是关键字继承, 写法是类型 猫 继承 动物(空格分隔),不是:。私有成员只能在类内部访问;受保护子类也能访问。抽象 类型不能直接新建,只能被继承;抽象方法子类必须实现。取值 十倍()用的时候不加括号:取值器.十倍(例子就是这么打印的)。
练习
- 写一个
类型 点 { 横: 数字; 纵: 数字 }加一个长度(): 数字(横纵平方和的平方根)。 - 让一个子类
继承它并重写长度。
第 7 课 泛型与名域
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
名域 | namespace | 给一组东西起个名字,避免撞名 |
导出 | export | 导出给别人用 |
导入 | import | 导入别人的东西 |
来自 | from | 导入 { 甲 } 来自 "./模块" |
作为 | as | 起别名:导入 { 甲 作为 乙 } / 类型断言 值 作为 类型 |
类型别名 | type | 也可以带泛型 |
接口 | interface | 也可以带泛型 |
本课符号
| 符号 | 用法 |
|---|---|
< > | 泛型参数与实参:函数 取大<元素类型>(…) |
. | 访问名域成员:工具.值 |
{ } | 名域体、导入列表 |
" " | 模块路径(必须是字符串,写英文/原样) |
例子的真源码
// 第 7 课:泛型与名域
//
// 关键字(英文对照):
// 函数(function) 返回(return) 数字(number) 字符串(string)
// 类型别名(type) 接口(interface) 名域(namespace) 导出(export)
// 导入(import) 来自(from) 作为(as) 常量(const) 局部变量(let) 任意(any)
// 符号:
// < > 泛型参数 / 类型实参
// :: 访问名域里的成员(TypeScript 用 . 也行,见例子)
// { } 名域体
//
// 期望输出:
// 泛型数组=1,7,3
// 泛型取大=9
// 名域里的值=42
// 名域里的函数=42
// 受限泛型=名字长度=2
//
// 说明:本课是【单文件】例子,所以没有真的 import。
// 导入/导出的可跑例子在 `tests-chts/proj/`(主.ts 与 数学.ts),
// 它由 `tests-chts/judge_proj.ps1` 真编译真运行。
// —— 泛型:一份代码服务多种类型 ——
函数 首项<元素类型>(表: 元素类型[]): 元素类型 | 未定义 {
返回 表.length > 0 ? 表[0] : undefined;
}
函数 取大<元素类型>(甲: 元素类型, 乙: 元素类型): 元素类型 {
返回 甲 > 乙 ? 甲 : 乙;
}
// —— 泛型带约束:要求这个类型必须有 length ——
接口 有长度 { length: 数字 }
函数 量长度<元素类型 继承 有长度>(东西: 元素类型): 数字 {
返回 东西.length;
}
console.log("泛型数组=" + [首项([1, 2, 3]), 首项([7, 8]), 3].join(","));
console.log("泛型取大=" + 取大(9, 3));
// —— 名域:给一组东西起个名字,避免撞名 ——
名域 工具 {
导出 常量 值 = 42;
导出 函数 取(): 数字 {
返回 工具.值;
}
}
console.log("名域里的值=" + 工具.值);
console.log("名域里的函数=" + 工具.取());
// —— 类型别名也可以泛型 ——
类型别名 表<元素类型> = 元素类型[];
常量 名字表: 表<字符串> = ["甲", "乙"];
console.log("受限泛型=名字长度=" + 量长度(名字表));跑出来的结果:
泛型数组=1,7,3
泛型取大=9
名域里的值=42
名域里的函数=42
受限泛型=名字长度=2讲解
- 泛型约束写法:
<元素类型 继承 有长度>—— 要求这个类型至少具备有长度的形状。 - 导入/导出的可跑例子不在本课(本课是单文件):见
tests-chts\proj\里的主.ts与数学.ts,它由判定脚本真编译真运行。 - 模块路径必须原样写(
"./数学"),它是运行时要解析的字符串,翻译了就找不到文件。
练习
- 写一个泛型
函数 末项<元素类型>(表: 元素类型[])。 - 在自己的
名域里放两个函数,用.调用它们。
第 8 课 基础标准库(诚实版)
⚠ ChineseTypeScript 没有「汉语标准库」。 与 Chinese++ 不同,这边只汉化了 语言关键字;console / Array / Map / JSON / Math 都是运行库的名字, 没有中文别名,一律写英文。
本课的关键字只出现在语法位置(常量 / 局部变量 / 遍历 / 函数 …), 库的成员名(.map / .filter / .length / .slice)全部照写英文。
例子的真源码
// 第 8 课:基础标准库怎么用(诚实版)
//
// ⚠ 先说清楚:**ChineseTypeScript 没有「汉语标准库」。**
// 这一点与 Chinese++ 不同 —— 那边连标准库都做了汉化层(`#包含 <向量>`、
// `标准::向量<整数>` 都能用)。TypeScript 这边只汉化了**语言关键字**,
// `console` / `Array` / `Map` / `JSON` 这些是**运行库的名字**,没有中文别名。
// 所以本课的关键字只在语法位置出现,库的成员名一律写英文 —— 英文永远可用。
//
// 本课关键字(英文对照):常量(const) 局部变量(let) 函数(function) 返回(return)
// 数字(number) 字符串(string) 遍历(of) 对于(for) 接口(interface) 类型别名(type)
// 异步(async) 等待(await)
// 符号:. 访问成员、[] 下标/取键、( ) 调用、=> 箭头、< > 泛型
//
// 期望输出:
// 数组映射=2,4,6
// 数组过滤=2
// 数组归约=6
// 字符串=你好世界 长度=4
// 字符串切片=世界
// Map取值=1
// Set去重=1,2,3
// JSON=小明,18
// Math=3
// 解构=1 2
// —— 数组:map / filter / reduce 都是英文方法名 ——
常量 数 = [1, 2, 3];
console.log("数组映射=" + 数.map((项) => 项 * 2).join(","));
console.log("数组过滤=" + 数.filter((项) => 项 % 2 === 0).join(","));
console.log("数组归约=" + 数.reduce((和, 项) => 和 + 项, 0));
// —— 字符串 ——
常量 名 = "你好世界";
console.log("字符串=" + 名 + " 长度=" + 名.length); // length 数的是 UTF-16 码元,汉字算 1
console.log("字符串切片=" + 名.slice(2));
// —— Map:键值对(比对象更适合频繁增删)——
常量 表 = new Map<字符串, 数字>();
表.set("甲", 1);
表.set("乙", 2);
console.log("Map取值=" + 表.get("甲"));
// —— Set:自动去重 ——
常量 集 = new Set([1, 2, 2, 3, 3]);
console.log("Set去重=" + [...集].join(",")); // ... 展开成数组
// —— JSON ——
接口 人 { 名字: 字符串; 年龄: 数字 }
常量 对象: 人 = JSON.parse('{"名字":"小明","年龄":18}');
console.log("JSON=" + 对象.名字 + "," + 对象.年龄);
// —— Math ——
console.log("Math=" + Math.max(1, 3, 2));
// —— 解构:把数组/对象拆开 ——
常量 [甲, 乙] = [1, 2];
console.log("解构=" + 甲 + " " + 乙);跑出来的结果:
数组映射=2,4,6
数组过滤=2
数组归约=6
字符串=你好世界 长度=4
字符串切片=世界
Map取值=1
Set去重=1,2,3
JSON=小明,18
Math=3
解构=1 2讲解
数组.map(函数)把每个元素变成新元素;filter挑出满足条件的;reduce把它们归成一个值。这三个是最常用的。字符串.length数的是 UTF-16 码元(一个汉字算 1,一个 emoji 算 2)。Map用.set/.get;Set自动去重;JSON.parse把字符串变成对象。[...集]用展开符号把Set摊成数组。
练习
- 用
filter挑出数组里的偶数并打印。 - 用
Map存三个人的年龄,再打印其中一个人的。
第 9 课 异步
本课关键字
| 汉语 | 英文 | 干什么 |
|---|---|---|
异步 | async | 标在函数前,让它返回 Promise |
等待 | await | 等一个 Promise 出结果 |
尝试 / 捕获 / 最终 | try / catch / finally | 异常处理 |
抛出 | throw | 抛异常 |
新建 | new | 新建 Error("…") |
本课符号
| 符号 | 用法 |
|---|---|
.then(...) | 接住 Promise 的结果(方法名英文) |
=> | 传给 .then 的函数 |
< > | Promise<数字> —— Promise 里装什么类型 |
例子的真源码
// 第 9 课:异步
//
// 关键字(英文对照):异步(async) 等待(await) 函数(function) 返回(return)
// 新建(new) 数字(number) 字符串(string) 空型(void) 常量(const) 局部变量(let)
// 符号:=> 箭头、< > 泛型、.then 链式调用、() 调用
//
// 期望输出:
// 先打印这一行
// 等待得到=42
// 出错也接住了
// 两件事都做完了
// —— 异步函数:函数前面加 异步,它就返回一个 Promise ——
异步 函数 取数(): Promise<数字> {
返回 42;
}
异步 函数 会失败(): Promise<数字> {
抛出 new Error("故意失败");
}
异步 函数 主(): Promise<空型> {
console.log("先打印这一行");
常量 值 = 等待 取数(); // 等待 = await:等 Promise 出结果
console.log("等待得到=" + 值);
尝试 { // 尝试 = try
等待 会失败();
} 捕获 (错误) { // 捕获 = catch
console.log("出错也接住了");
}
}
// 主() 返回 Promise,用 .then 接住它(.then 是英文方法名)
主().then(() => {
console.log("两件事都做完了");
});跑出来的结果:
先打印这一行
等待得到=42
出错也接住了
两件事都做完了讲解
异步 函数里写等待 某个Promise,代码读起来是同步的,实际是异步的。.then是 Promise 的方法(英文名);也可以用等待在另一个异步 函数里接。- 异步出错用
尝试 / 捕获接(等待抛出的异常能被捕获抓到)。
练习
- 写一个
异步 函数 延时(毫秒: 数字),用new Promise+setTimeout实现。 - 用
尝试/捕获接住一个失败的等待。
附录 A 符号用法速查
一句话:TypeScript 这边符号一个都没换。
| 符号 | 名字 | 典型写法 | 说明 |
|---|---|---|---|
// | 行注释 | // 说明 | 到行尾 |
/* */ | 块注释 | /* 多行 */ | |
; | 分号 | 常量 甲 = 1; | 可省,但建议写 |
{ } | 花括号 | 函数 甲() { … } | 包代码块;对象/名域/类体也用它 |
( ) | 圆括号 | 如果 (甲 > 乙) / 相加(1, 2) | 条件、参数、调用 |
[ ] | 方括号 | 表[0] / 数字[] | 取下标(从 0)/ 数组类型 |
. | 点 | 对象.名字 | 访问成员 |
: | 冒号 | 甲: 数字 | 类型标注 |
=> | 箭头 | (甲) => 甲 + 1 | 箭头函数 |
< > | 尖括号 | 取大<数字>(…) / Promise<数字> | 泛型 |
| | 竖线 | 字符串 | 未定义 | 联合类型 |
& | 与号 | 甲 & 乙 | 交叉类型(同时具备) |
? | 问号 | 名字?: 字符串 / 甲 ? 乙 : 丙 | 可选属性 / 三元 |
?. | 可选链 | 配置.名字?.length | 空就短路成 undefined |
?? | 空值合并 | 可能空 ?? "默认" | 只对 空值/undefined 生效 |
! | 感叹号 | 甲! / !真值 | 非空断言 / 逻辑非 |
... | 省略号 | [...表] / (...数: 数字[]) | 展开 / 剩余参数 |
` | 反引号 | `你好,${名字}` | 模板串,${} 里放表达式 |
= += -= | 赋值 | 甲 += 1 | |
++ -- | 自增自减 | 甲++ | |
=== !== | 严格相等/不等 | 甲 === 乙 | 推荐 |
== != | 松散相等/不等 | 甲 == 乙 | 会做类型转换 |
&& || | 与或 | 真 && 假 | 没有汉语替代拼写 |
+ - * / % | 算术 | 甲 % 2 | + 也拼接字符串 |
附录 B 关键字总表
下表是从 docs\中文对照表.md 原样切进来的 —— 那张表由 tsc/cmd/genzhkeywords 从编译器真正在用的词表(zh_keywords.txt)自动生成, 并有判定脚本防它漂移。放进教程里是为了让你不用来回翻文件。
一、关键字(85 个)
| 中文 | 英文 | 中文 | 英文 |
|---|---|---|---|
抽象 | abstract | 是类型 | is |
访问器 | accessor | 取键 | keyof |
任意 | any | 局部变量 | let |
作为 | as | 模块 | module |
断言 | assert | 名域 | namespace |
断言式 | asserts | 永不 | never |
异步 | async | 新建 | new |
等待 | await | 空值 | null |
大整数 | bigint | 数字 | number |
布尔 | boolean | 对象 | object |
跳出 | break | 遍历 | of |
情形 | case | 输出 | out |
捕获 | catch | 重写 | override |
类型 | class | 软件包 | package |
常量 | const | 私有 | private |
构造器 | constructor | 受保护 | protected |
跳过 | continue | 公开 | public |
调试器 | debugger | 只读 | readonly |
声明 | declare | 需要 | require |
默认 | default | 返回 | return |
推迟 | defer | 满足 | satisfies |
删除 | delete | 设值 | set |
执行 | do | 静态 | static |
否则 | else | 字符串 | string |
枚举 | enum | 父类 | super |
导出 | export | 选择 | switch |
继承 | extends | 符号 | symbol |
假值 | false | 自身 | this |
最终 | finally | 抛出 | throw |
对于 | for | 真值 | true |
来自 | from | 尝试 | try |
函数 | function | 类型别名 | type |
取值 | get | 取类型 | typeof |
全局 | global | 未定义 | undefined |
如果 | if | 唯一 | unique |
立即 | immediate | 未知 | unknown |
实现 | implements | 使用 | using |
导入 | import | 变量 | var |
属于 | in | 空型 | void |
推断 | infer | 每当 | while |
是实例 | instanceof | 伴随 | with |
接口 | interface | 产出 | yield |
内建 | intrinsic |
那张表还有第二~六节:译名定则、这些仍然必须写英文、保留字副作用、 关键字当属性名、一个完整例子。见
docs\中文对照表.md。
附录 C 常见坑
| 坑 | 现象 | 怎么办 |
|---|---|---|
| 单字关键字不存在 | 写 类 编不过 | 汉语关键字不少于两个字:class 是 类型,不是 类 |
| 想当然的译名 | 写 超级、可空 报「找不到名字」 | 按附录 B 的表写:super 是 父类,null 是 空值 |
未定义 当值不行 | 已修(见下) | 值与类型两种位置都能写 未定义;空值(null) 同理 |
| 库的成员是英文 | 写 表.尺寸 报「没有该属性」 | 运行库的名字没汉化:用 表.length、console.log |
| 模块路径写中文 | 编译过但运行时找不到模块 | 路径是字符串,必须原样:来自 "./数学" |
选择 忘 跳出 | 会串到下一个 情形 | 每个 情形 末尾写 跳出 |
== 与 === 混用 | 出现古怪的类型转换 bug | 一律用 === / !== |
| ` | ` 当默认值 | |
忘了 --rewriteRelativeImportExtensions | 显式写 "./数学.ts" 时产物路径不对 | 推荐无扩展名导入("./数学"),见 tests-chts\proj\ |
案例:未定义 当值曾经不行 —— 现在修好了
这条值得留着,因为它演示了「让汉语关键字真正等价于英文」会卡在哪些地方。
症状:常量 甲 = 未定义; 报 TS2304 找不到名字;而 : 未定义(当类型)完全正常。
根因:null / true / false 是 JS 的字面量,词法关键字直接变成字面量节点, 打印器再从种类取规范拼写 —— 所以 空值 与 null 完全等价。 但 undefined 在 JS 里不是字面量,而是全局对象上的一个名字: 上游把它按文字注册成全局符号(checker.go 里的 c.globals["undefined"]), 值位置也按普通标识符解析。于是汉语拼写两头落空:
- 解析时查
c.globals["未定义"]→ 查不到; - 产物又会把
未定义原样打印成标识符,而 JS 里没有这个全局名 → 运行时ReferenceError。
修法(两处,都落在边界上):
- 解析器:值位置把标识符文本归一化成规范拼写
undefined(parser.parseUndefinedKeywordExpression)—— 解析与类型一次解决; - 打印器:当节点文本是
undefined而源码拼写不是时,用节点文本而不是切源码 (printer.getTextOfNode)—— 否则汉语拼写照样进产物。
关键约束:只归一化【值位置】。 属性名与成员名是运行时的真实属性名,必须原样保留:
常量 戊 = { 未定义: 1 }; // 属性名就叫「未定义」,产物里也是 未定义
console.log(戊.未定义); // 成员名同理同一类问题会在多个位置出现——修的时候要一个位置一个位置地扫: 值位置、类型位置、数组字面量、默认参数、模板串、以及对象简写属性 ({ 未定义 } 里「名字就是值引用」,也必须归一化,否则报 TS18004)。 上面这几种位置现在都验过,由 tests-chts\undefined.ts 靶子 + 判定第 11 节 永久钉住:它同时断言「值位置与简写属性是 undefined」与「属性名仍然是 未定义」。
顺带说:为什么要真编译
这条边界不是读文档读出来的,是真编译撞出来的。 如果教程只是照着关键字表抄一遍、不实际编译运行,读者会抄到一段跑不起来的中文代码。 这也是为什么本教程的每个例子都由 tests-chts\judge_tutorial.ps1真编译、真运行、逐行核对输出,而且那个判据自带自测(改坏必须被抓)。
那,英文怎么办?
照样用,而且可以混着写。 例如:
常量 表: Array<number> = [1, 2, 3]; // 汉语关键字 + 英文类型名
函数 加倍(值: number): number { 返回 值 * 2; }
console.log(表.map(加倍));汉语不是替换英文,是并行多出来的一种拼写。 这也是为什么「随便拿一个开源 TypeScript 项目,用我们的编译器编,照样编得过」。