ts随笔:基础与类型系统

ts随笔:基础与类型系统

本文主要记录一些关于 TypeScript 的 API 使用方法和一些底层的原理。

供自己以后查漏补缺,也欢迎同道朋友交流学习。


原文地址

墨渊书肆/ts随笔:基础与类型系统


介绍

TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集,即 TypeScript 添加了额外的类型定义和一些其他功能,最终会被编译成普通的 JavaScript 代码。它旨在解决 JavaScript 在大型应用程序开发中遇到的不足,如缺少静态类型检查、接口、类以及模块等特性。由于其提供的额外安全性和开发效率提升,TypeScript 越来越受到开发者的欢迎。

特点

TypeScript 的主要特点包括:

  1. 静态类型检查:在编译阶段就能发现类型错误,有助于开发者提前发现并修正错误。
  2. 类和接口:支持面向对象编程,可以定义类、继承和接口,提高代码的复用性和结构化。
  3. 模块:通过导入和导出语句支持代码的模块化,有利于组织和管理大型项目。
  4. 泛型:允许创建可重用的组件,这些组件可以在不指定具体类型的前提下操作多种数据类型。
  5. 装饰器:提供了一种在类声明、方法、访问器、属性或参数上添加元数据或修改类的行为的方式。

安装和配置

安装 TypeScript

  1. 前提条件:确保你的系统已安装 Node.js,因为 TypeScript 依赖于 Node.js 的 npm 包管理器进行安装。

  2. 全局安装 TypeScript:打开终端或命令提示符,运行以下命令来全局安装 TypeScript:

bash 复制代码
## 使用 npm 全局安装
npm install -g typescript
## 或使用 yarn 全局安装
yarn global add typescript

## 验证 TypeScript 是否安装成功
tsc --version

创建 TypeScript 项目

bash 复制代码
## 初始化项目:在你希望创建项目的目录下,运行下面命令
npm init

## 安装依赖
npm install --save-dev typescript

配置 TypeScript

bash 复制代码
## 在项目根目录下创建 tsconfig.json
npx tsc --init

配置完成后,你可以在 tsconfig.json 中设置目标 JavaScript 版本(target)、模块系统(module)、源码目录(include)、排除目录(exclude)等。

json 复制代码
{
  "compilerOptions": {
    "target": "es2020",
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "sourceMap": true
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules"]
}

随着 ES2025、ES2026 等新标准推进,TypeScript 的 target 和 lib 选项也会不断跟进最新的内置对象和语法特性(例如 Iterator Helpers、Set 扩展、JSON 模块、Temporal 等),只要保持 TypeScript 版本和编译目标的升级,就可以在项目中获得这些新能力的类型支持。

编译 TypeScript
bash 复制代码
npx tsc

这将根据 tsconfig.json 中的配置编译你的 TypeScript 文件,并将编译后的 JavaScript 文件输出到指定的目录。

基础

内置对象

TypeScript 继承了 ECMAScript 的所有内置对象。以下是一些主要的内置对象:

  1. Boolean:布尔值对象,用于封装一个布尔值。
  2. Error:错误对象,用于生成错误信息。
  3. Date:日期对象,处理日期和时间。
  4. RegExp:正则表达式对象,用于匹配文本模式。
  5. Number:数值对象,封装数字并提供相关操作方法。
  6. String:字符串对象,封装字符串并提供操作字符串的方法。

如果在浏览器环境中使用 TypeScript,还会有额外的针对 DOM 和 BOM 的内置对象:

  • Document:代表整个 HTML 文档的对象。
  • HTMLElement:基类,表示任何 HTML 元素。
  • Event:事件对象,封装了发生的事件信息。
  • NodeList:非实时集合,包含了按照指定选择器选取的一组节点。

在 TypeScript 中,你不需要特别引入这些内置对象,它们可以直接在代码中使用。例如,创建一个错误对象可以简单地写作:

typescript 复制代码
const error = new Error();

从 ES2025、ES2026 开始,诸如 Temporal(全新的日期时间 API)、Iterator Helpers(for-of 可用的一组迭代器辅助方法)、Set 扩展方法等也会逐步进入主流运行时环境。TypeScript 会在对应的 lib.*.d.ts 中为这些内置对象补充类型定义,只要升级 TypeScript 并将 target/lib 指向更新的 ES 版本,就可以在编辑器中获得这些最新内置对象的完整类型提示。

原始数据类型

在 TypeScript 中,使用原始数据类型的基本方法如下:

typescript 复制代码
// 布尔值(boolean)
const isDone: boolean = false;

// 数值(number)
const decimal: number = 6; // 十进制
const binary: number = 0b1010; // 二进制
const octal: number = 0o744; // 八进制
const hex: number = 0xf00d; // 十六进制

// 字符串(string)
const name: string = "niunai";
const fullName: string = `niunai, age is ${32}`;

// 空值(null)
const status: null | undefined = null;

// 未定义(undefined)
const u: undefined = undefined;

// 符号(Symbol)
const sym1: symbol = Symbol();
const sym2: symbol = Symbol("key");

// 大整数(BigInt)(用于表示大于 2^53 - 1 的整数)
const bigInt: bigint = 1020023339011239123n; // 注意结尾的 n

随着 Temporal 在 ES2025 附近进入标准,你在处理日期和时间时可以使用更加精确和可靠的类型,例如:

typescript 复制代码
// 假设运行时和 TypeScript lib 已经支持 Temporal
// 下面的类型定义就可以通过官方声明文件获得提示
// const now: Temporal.ZonedDateTime = Temporal.Now.zonedDateTimeISO();

任意值 any

在 TypeScript 中,any 类型是一个特殊类型,表示可以是任何类型。当你不清楚一个值的具体类型,或者希望跳过类型检查时,可以使用 any 类型。但是,过度使用 any 会削弱类型安全优势,因此应谨慎使用。

typescript 复制代码
declare function someFunction(): any;

const anyValue: any = someFunction();

数组类型

在 TypeScript 中,数组类型可以明确指定每个元素的类型,以增强类型安全。有几种不同的方式来定义数组类型:

typescript 复制代码
// 类型后缀表示法(最常用)
const numberArray: number[] = [1, 2, 3];
const stringArray: string[] = ["a", "b", "c"];

// 泛型数组类型 Array<T>
const numberArray2: Array<number> = [1, 2, 3];
const stringArray2: Array<string> = ["a", "b", "c"];

// 元组类型 Tuple
const tuple: [string, number, boolean] = ["hello", 11, true];

// 只读数组 ReadonlyArray<T>
const readonlyArray: ReadonlyArray<number> = [1, 2, 3];
// readonlyArray.push(4); // 这会导致编译错误

// 类型推断
const mixedArray = [1, "two", true]; // 推断为 (number | string | boolean)[]
const explicitlyTypedArray: (number | string)[] = [1, "two"]; // 明确指定联合类型数组

在未来 ES2025/ES2026 的 Iterator Helpers 提案落地后,数组和可迭代对象会多出一批链式调用方法,例如 mapfiltertake 等。TypeScript 会通过更精细的泛型定义,对这些方法的返回值进行类型推断,从而在保持链式风格的同时保证类型安全。

函数类型

函数类型用于描述函数的输入(参数类型)和输出(返回值类型)。定义函数类型主要有两种方式:匿名函数类型和具名函数声明。

匿名函数类型的定义

匿名函数类型直接定义了函数参数和返回值的类型,不绑定到特定的函数名。这种类型通常用于类型注解或变量声明中。

typescript 复制代码
// 函数类型定义
let add: (x: number, y: number) => number;

// 实现该类型的函数赋值给变量
add = function (x: number, y: number): number {
  return x + y;
};
具名函数声明

具名函数声明不仅定义了函数的类型,还为函数赋予了一个名字,可以在代码中直接调用。

typescript 复制代码
function add2(x: number, y: number): number {
  return x + y;
}

这里,add2 函数被明确声明为接收两个 number 参数并返回 number 类型值的函数。

函数重载

TypeScript 还支持 函数重载,允许为同一个函数名提供多个类型签名,根据传入参数的不同自动匹配合适的实现。

typescript 复制代码
function print(value: string): void;
function print(value: number): void;

function print(value: string | number): void {
  console.log(value);
}

print("Hello");
print(123);

对象类型 - 接口 interface

接口是 TypeScript 中定义和强制执行对象结构的强大工具。它们不仅能够确保代码中对象的正确性,还能帮助文档化代码,提升开发者的理解速度和代码的可维护性。

基本接口定义
typescript 复制代码
interface Person {
  firstName: string;
  lastName: string;
  age?: number; // 问号表示此属性是可选的
}

// 使用接口
let person: Person = {
  firstName: "John",
  lastName: "Doe",
};
扩展接口

接口还可以扩展其他接口,实现接口的复用和层次化定义:

typescript 复制代码
interface Employee extends Person {
  employeeId: number;
  department: string;
}

// 使用扩展的接口
let employee: Employee = {
  firstName: "Jane",
  lastName: "Doe",
  employeeId: 123,
  department: "HR",
};

函数类型接口

接口也可以用来定义函数的参数类型或返回值类型:

typescript 复制代码
interface GreetingFunc {
  (name: string): string;
}

let sayHello: GreetingFunc = function (name: string): string {
  return `Hello, ${name}!`;
};

索引签名

接口可以使用索引签名定义可索引的对象,比如数组或字典类型:

typescript 复制代码
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray = ["Bob", "Alice"];

Type 类型别名

在 TypeScript 中,type 是另一种创建新类型的手段,与 interface 有着相似之处,但也存在关键区别。type 主要用于定义 类型别名(Type Aliases)、联合类型(Union Types)、元组类型(Tuple Types)、字面量类型(Literal Types)以及 枚举(Enums,虽然通常直接使用 enum 关键字)。

类型别名(Type Aliases)

类型别名用于给已存在的类型起一个新的名字,增加代码的可读性。它可以用来替代任何类型,包括基本类型、接口、联合类型等。

typescript 复制代码
type StringOrNumber = string | number;

function logValue(value: StringOrNumber) {
  console.log(value);
}

logValue("Hello");
logValue(123);
联合类型(Union Types)

联合类型表示一个值可以是几种类型之一。使用 type 定义联合类型可以更易于阅读。

typescript 复制代码
type UserID = string | number;

function getUser(id: UserID) {
  // ...
}
元组类型(Tuple Types)

元组类型允许表示一个已知元素数量和类型的数组。使用 type 定义元组类型可以提供更清晰的意图。

typescript 复制代码
type Coordinate = [number, number];

const point: Coordinate = [10, 20];
字面量类型(Literal Types)

字面量类型允许你指定一个具体的值作为类型,这对于精确控制变量的可能值很有用。

typescript 复制代码
type Color = "red" | "green" | "blue";

function setBackgroundColor(color: Color) {
  // ...
}

setBackgroundColor("red"); // 正确
// setBackgroundColor("yellow"); // 错误
与接口的区别
  • interface 更侧重于描述对象结构,可以被实现(通过类),可以扩展,适合描述具有相同属性或方法的多个实体的公共结构。
  • type 提供了更多关于类型定义的灵活性,可以用于联合类型、元组、字面量类型等,不支持实现或扩展,但可以被其他类型别名再次引用。

枚举 enum

在 TypeScript 中,枚举(Enum)是一种数值或字符串常量的集合,它为一组相关的值提供了友好的名字。

typescript 复制代码
// 基本枚举:默认情况下,第一个成员的值为 0,之后每个成员依次递增 1。
enum Color {
  Red,
  Green,
  Blue,
}

console.log(Color.Red); // 输出 0
console.log(Color[0]); // 输出 "Red"

// 指定成员值
enum Color2 {
  Red = 8,
  Green = 16,
  Blue = 32,
}

console.log(Color2.Red); // 输出 8

// 字符串枚举
enum Color3 {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
}

console.log(Color3.Red); // 输出 "RED"

元组 Tuple

在 TypeScript 中,元组(Tuple)是一种特殊的数据类型,它允许你创建一个固定长度的数组,其中每个元素可以有不同的类型。

typescript 复制代码
// 基本定义
let myTuple: [string, number, boolean];

// 初始化和访问
myTuple = ["Alice", 30, true];
console.log(myTuple[0]); // "Alice"
console.log(myTuple[1]); // 30

// 解构赋值
let [userName, age, isAdmin] = myTuple;
console.log(userName);
console.log(age);
console.log(isAdmin);

// 可选元素与默认值
let optionalTuple: [string, number?, boolean] = ["Bob"];

// 组合元组
let firstTuple: [string, number] = ["Tom", 25];
let secondTuple: [boolean] = [true];
let combinedTuple: [...firstTuple, ...secondTuple] = ["Tom", 25, true];

类型断言

类型断言(Type Assertion)是一种告诉编译器"我相信这个值是某种类型"的方式。它不会修改变量的实际类型,只是让编译器按照你所断言的类型去处理这个值。

typescript 复制代码
let someValue = "this is a string";
let strLength1: number = (someValue as string).length;
let strLength2: number = (<string>someValue).length;

在实际项目中,随着 2025/2026 年新的 Web 与语言特性落地,你经常会在类型声明还不完全的时候先使用类型断言"兜底",待官方声明文件补全之后再逐步收紧类型。

声明文件

声明文件(.d.ts 文件)是 TypeScript 与非 JavaScript 代码交互的关键桥梁,它们使得 TypeScript 能够为 JavaScript 代码提供类型检查和智能提示,提升开发体验和代码质量。

声明文件的用途

  1. 提供类型信息:对于没有类型注释的 JavaScript 库,声明文件提供必要的类型信息,让 TypeScript 知道函数、对象、模块的形状。
  2. 兼容性:允许 TypeScript 项目无痛地引用和使用 JavaScript 库,保持代码的强类型检查和智能提示。
  3. 定义全局变量和模块:声明文件可以用来定义全局变量(如 window 上的挂载点)或声明外部模块的存在。

如何创建和使用声明文件

创建自定义声明文件:

typescript 复制代码
// myLib.d.ts
declare function myFunction(name: string): void;
export default myFunction;

使用第三方声明文件:

bash 复制代码
npm install @types/node --save-dev

声明全局变量:

typescript 复制代码
declare var jQuery: any;

声明模块:

typescript 复制代码
declare module "myModule" {
  export function doSomething(): string;
}

2025-2026 新模块与声明文件

随着 ES2025 的 JSON 模块、ES2026 的 import defer 等提案逐步标准化,运行时会出现更多"非传统脚本文件"的导入方式,例如:

typescript 复制代码
// 假设运行时已经原生支持 JSON 模块
import config from "./config.json" with { type: "json" };

// 假设浏览器开始支持 import defer 语法
import defer "./heavy-module.js";

对于这些新特性,对应的类型信息会由 TypeScript 团队在 lib.d.tsdom.d.ts 等声明文件中同步维护。项目只要升级到较新的 TypeScript 版本,并合理配置 modulemoduleResolution,就可以在编辑器中获得这些新模块语法的类型提示和错误检查。

相关推荐
用户73992986959722 小时前
DeepSeek/GPT-4 落地实战:我如何用 Node.js + AI 手搓一个“面试神器”
面试
牛奶2 小时前
JS随笔:浏览器 API(DOM 与 BOM)
前端·javascript·面试
用泥种荷花2 小时前
【LangChain.js学习】 会话记忆(临时/长期)全解析
前端
慢慢长大的孩子2 小时前
原生Android开发与JS桥开发对比分析
前端·后端
爱勇宝2 小时前
2026年前端生存规划:只会写页面的人,正在被悄悄淘汰
前端·后端·架构
牛奶2 小时前
JS随笔:异步编程与事件循环
前端·javascript·面试
牛奶2 小时前
JS随笔:数据结构与集合
前端·javascript·面试
小陆猿2 小时前
股票实时行情Echarts动态图表
前端·javascript
Dilettante2582 小时前
React Server Components 全链路解析:Next.js 构建产物、导航流程与 Payload 格式
前端·next.js