TypeScript 正在经历一场由 AI 驱动的“爆发式”增长,甚至在活跃度上已经超越了 Python,成为 GitHub 上最活跃的编程语言。 作为JAVA 开发者,有必要了解并熟悉一下typescript

TypeScript 的设计者有意借鉴了 Java、C# 等语言的一些语法元素,降低了学习曲线,但底层机制与JAVA、C#有着本质的不同

  • 是 JavaScript 的超集,底层基于原型链 (Prototype-based Inheritance)
  • 倾向于函数式编程 (Functional Programming)声明式编程 (Declarative Programming)
  • 对象创建时更倾向于用对象字面量、工厂函数等方式
  • 结构化类型系统、联合类型、类型守卫、any 等特性,使其类型系统比 Java 的更加灵活和动态。但 TypeScript 的类型系统在编译成 JavaScript 后会完全消失(Type Erasure)

控制流分析CFA

类型收窄

  • 控制流分析的核心机制类型收窄,本质是一个缩小类型范围的过程
  • 收窄手段:
    • 类型守卫函数:自定义收窄逻辑
      • type-guard functions (for anything)
      • Assertion Functions【断言函数】
    • 与原生逻辑的融合:直接利用 JavaScript 原生的布尔逻辑(typeof、instanceof、in)
      • typeof (for primitives)
      • instanceof(for classes)
      • “property” in object (for objects)
特性 原生布尔逻辑判断 用户自定义类型守卫
触发方式 使用 ifswitch 等原生逻辑 调用你定义的特定函数
适用场景 简单、一次性的类型判断 复杂逻辑、需要复用的判断
优点 开箱即用,无需额外代码 提高代码复用性,封装复杂判断
本质 编译器自动推断 开发者显式告知编译器
// 1. 定义类型  
type Fish = { swim: () => void };  
type Bird = { fly: () => void };  
  
// 2. 创建具体的实例 (关键:这里初始化了变量)  
const myFish: any = {  
    swim: () => {  
        console.log("鱼在水里游来游去 🐟");  
    }  
};  

// 定义一个类型守卫函数  
function isFish(pet: Fish | Bird): pet is Fish {  
    // 这里的逻辑可以非常复杂  
    return (pet as Fish).swim !== undefined;  
}  
  
// 使用自定义的守卫函数
if (isFish(myFish)) {  
    // TypeScript 知道 pet 是 Fish    myFish.swim();  
}  

//使用javascript原生布尔逻辑判断
if ("swim" in myFish) {  
    myFish.swim();  
}

Assertion Functions

class SuccessResponse {  
    constructor(public data: any) {  
        this.data = data;  
    }  
}  
  
function assertResponse(obj: unknown): asserts obj is SuccessResponse {  
    if (!(obj instanceof SuccessResponse)) {  
        throw new Error("fail");  
    }  
}  
  
  
try {  
    const validObj = new SuccessResponse({message: "成功"});  
    assertResponse(validObj);  
    console.log("检查通过:", validObj.data);  
} catch (error) {  
    console.error((error as Error).message);  
}  
  
// 情况 B:传入错误的类型  
try {  
    const invalidObj = {wrong: "data"};  
    assertResponse(invalidObj);  
    console.log(invalidObj.data);  
} catch (error) {  
    console.error("捕获到错误:", (error as Error).message); // 输出: 捕获到错误: fail 
}

短路求值

  • 使用 && (逻辑与) 进行缩小
const input: string | number = "Hello World";  
  
// 返回11,但是如果input=42,则返回42
const inputLength = (typeof input === "string" && input.length) || input;

固定对象中的子字段的类型

const data = {  
    name: "you"  
} as const;  
  
console.log(data.name)

interfaces: 类型即行为

Almost everything in JavaScript is an object and interface is built to match their runtime behavior

  • 用来描述对象的形状(属性和方法),能被其它对象继承
  • TypeScript 的 interface 是一种静态的契约,它不改变 JavaScript 的运行时本质,而是通过“观察”和“描述”对象在运行时实际拥有的属性和方法,来为动态的 JavaScript 提供类型安全。

类型系统

基础类型

JavaScript基本数据类型
Type Description Example
string Represents textual data. let name: string = "Alice";
number Represents both integers and floating-point numbers. let age: number = 30;
boolean Represents logical values: true or false. let isActive: boolean = true;
null Represents an intentional absence of value. let data: null = null;
undefined Represents a variable that has not been initialized. let notDefined: undefined = undefined;
The Special “Big” Primitive

Introduced in ES2020, this is used for very large numbers that exceed the safe integer limit.

  • bigint: Represents whole numbers larger than Number.MAX_SAFE_INTEGER.
    • Note: It is distinct from number and cannot be mixed with numbers in operations without conversion.
    • Example: let bigNum: bigint = 100n;
The Unique Identifier Primitive
  • symbol: Represents a unique, immutable value, often used as object property keys to avoid naming collisions.
    • Example: let sym: symbol = Symbol("description");
TypeScript特有原始类型
Type Description Usage Context
void Represents the absence of any value. Used as the return type for functions that don’t return a value.
never Represents values that never occur. Used for functions that always throw an exception or have infinite loops.
unknown Represents a value whose type is not known. A safer alternative to any. You must narrow the type before using it.
any Represents any JavaScript value. Disables type checking. Use sparingly to avoid losing type safety.

💡 Key Distinction: any vs unknown

  • any: Turns off TypeScript’s type checking. You can do anything with an any value (e.g., any.toFixed(), any.prop).
  • unknown: Is type-safe. You must prove the type to TypeScript (via type guards or assertions) before you can perform operations on it.

javascript常用内置对象

  • Date
  • Error
  • Array
  • Map
  • Set
  • Regexp
  • Promise

Type Literals

  • Object: {field:string}
  • Function: (arg:number)=>string
  • Arrays: string[] or Array<String>
  • Tuple: [string,number]

调用签名 (): ReturnType

描述一个对象(通常是函数)被直接调用时的行为。

interface RandomNumberGenerator {  
    seed: number; // 它还可以有其他属性  
  
    (): number; // 调用它就返回一个随机数  
}  
  
const randomGen: RandomNumberGenerator = Object.assign(  
    function (): number {  
        return Math.random();  
    },  
    {seed: Date.now()}  
);  
  
// randomGen.seed = 12345; // 可以访问属性  
const num = randomGen(); // 可以调用  
console.log(num, randomGen.seed);

构造签名 new(s: string): InstanceType

描述一个(或构造函数)被 new 关键字调用时的行为

构造签名示例

const UserFactory: ClassFactory<User> = User;
这行代码巧妙地利用了 TypeScript 的类型兼容性规则,将一个User)本身作为,赋给了一个描述构造函数类型ClassFactory<User>)的变量。这使得 UserFactory 变量可以像 User 类一样被 new 调用,从而实现了一种“类工厂”的效果。

interface ClassFactory<T> {  
    new(...args: any[]): T; // 可以接收任意参数,返回类型 T 的实例  
}  
  
// 使用它来约束一个类  
class User {  
    constructor(public name: string, public age: number) {  
    }  
}  
  
// 定义一个工厂,它符合 ClassFactory<User> 的要求  
const UserFactory: ClassFactory<User> = User;  
  
// 使用工厂创建实例  
const user = new UserFactory("Alice", 30);  
console.log(user); // { name: 'Alice', age: 30 }

调用签名和构造签名示例

// 定义一个接口,同时包含调用签名和构造签名  
interface MixedObject {  
    // 构造签名:new 时接收一个字符串,返回一个实例  
    new(s: string): MixedObjectInstance;  
  
    // 调用签名:直接调用时,也接收一个字符串,返回一个实例  
    (s: string): MixedObjectInstance;  
}  
  
// 定义实例的类型  
class MixedObjectInstance {  
    constructor(public message: string) {  
    }  
}  
  
// 实现这个接口:通常用一个函数来实现,因为它既可以被 new,也可以被直接调用  
function MixedObjectImpl(s: string): MixedObjectInstance {  
    // 检查是被 new 调用还是直接调用  
    if (new.target) {  
        // 如果是 new 调用,new.target 会是 MixedObjectImpl        console.log("Called with new");  
        return new MixedObjectInstance(s);  
    } else {  
        // 如果是直接调用  
        console.log("Called without new");  
        return new MixedObjectInstance(s);  
    }  
}  
  
// 将函数类型断言为接口类型  
const MixedObject: MixedObject = MixedObjectImpl as MixedObject;  
  
// 设置原型链,使 new 能正确工作  
MixedObject.prototype = MixedObjectInstance.prototype;  
  
// 使用  
const instance1 = new MixedObject("via new"); // 输出 "Called with new"console.log(instance1.message); // via new  
  
const instance2 = MixedObject("via call"); // 输出 "Called without new"console.log(instance2.message); // via call

索引签名[key: string]: Type

TypeScript 支持 [key: string]: number 这种 索引签名(Index Signature) 语法,其核心目的非常明确: 为了填补“静态类型检查”与“JavaScript 动态特性”之间的鸿沟

为了让你能够安全地拥抱 JavaScript 的动态性。它让你在面对“未知”时,依然能划定 “类型边界”,防止代码变得不可控。

interface MyObj {  
    // 明确声明的属性  
    knownProp: string;  
  
    // 索引签名:允许任意其他字符串属性,但值必须是 string    
    [key: string]: string;  
}  
  
const obj: MyObj = {  
    knownProp: "张三",     // 明确声明的属性,OK  
    dynamicA: "2",      // 未声明的动态属性,OK (因为值是 number)    dynamicB: "3"       // 未声明的动态属性,OK (因为值是 number)};  
  
console.log(obj)

声明合并(Declaration Merging)

当你在同一个作用域内,多次使用 interface 关键字定义同一个名字的接口时,TypeScript 会自动将这些定义合并成一个单一的接口。这个最终的接口会包含所有声明中定义的成员。

TypeScript 允许你通过多次声明同一个 interface 来逐步构建它的形状,所有声明会被自动合并成一个完整的接口。这是一种安全、灵活的类型扩展机制,尤其在增强第三方库类型时非常有用。

// 第一次声明  
interface User {  
    name: string;  
}  
  
// 第二次声明(同名)  
interface User {  
    age: number;  
}  
  
// 第三次声明(同名)  
interface User {  
    email: string;  
}  
  
// 最终效果:User 接口包含了所有属性  
const user: User = {  
    name: "Alice",  
    age: 30,  
    email: "alice@example.com"  
}; // ✅ 完全合法  
  
console.log(user)  
  
declare global {  
    namespace Express {  
        interface Request {  
            user?: { id: string; name: string };  
        }  
    }  
}

实际应用场景

这个特性在以下场景中非常有用:

1. 扩展现有库的类型定义
  • 当你使用第三方库(如 jQuery、Express)时,它的类型定义文件(.d.ts)可能没有包含某个插件添加的方法。
  • 你可以通过再次声明同名接口来安全地扩展它,而无需修改原库的代码。
2. 模块化定义大型接口
  • 对于非常复杂的接口(比如一个大型配置对象),你可以把它拆分成多个小的 interface 声明,放在不同的文件或逻辑块中,最后它们会自动合并。
3. 条件性地添加属性(配合条件类型)
  • 虽然不常用,但可以在不同条件下声明同名接口来动态构建类型。

注意事项

  • 只适用于 interfacetypeclassenumfunction 都不能通过这种方式合并(函数和命名空间有自己特殊的合并规则)。
  • 作用域必须相同:只有在同一个作用域(如同一个文件或通过 declare global 在全局作用域)内的同名 interface 才会合并。
  • 方法和属性会累加:如果多次声明中有同名的属性或方法,它们的类型必须兼容,否则会报错。

Types

type 与 interface 针对对象形状定义:两者几乎等价, 但type 的表达能力更强大

  • 原始类型别名 type ID = string;
  • Object Literal Type: type Location = { x: number, y: number }
  • 联合类型(Union): type Status = "success" | "error" | "loading";
  • 元组Tuple:
    • type Point = [number, number];
    • type Data = [location: Location, timestamp: string]
  • 交叉类型 &type Dog = Animal & { bark(): void };
  • Type from Func Return: type xx=ReturnType<typeof xxFunc>;
  • Type from module: type data:import("./data").data
  • Type from indexing: type Response = {data:{...}};type Data=Response["data"];
  • Type from Value: const data={};type Data=typeof data
  • 条件类型type IsString<T> = T extends string ? true : false;
  • 映射类型(Mapped Types)type Readonly<T> = { readonly [K in keyof T]: T[K] };
  • Template Union Type:
特性 作用 示例用途
条件类型 根据类型关系选择类型 提取返回值、过滤联合类型、类型守卫增强
映射类型 遍历键生成新类型 将属性设为可选/只读/可变、深度 Partial、Immutable 工具

条件类型示例

// 条件类型:如果是函数类型,则返回其返回值类型;否则返回 never
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;  
  
// 测试函数  
function fetchData(): string {  
    return "Hello";  
}  
  
function add(a: number, b: number): number {  
    return a + b;  
}  
  
const value1: GetReturnType<typeof fetchData> = "Hello";     // ✅ stringconst value2: GetReturnType<typeof add> = 42;                // ✅ number  
console.log(value1, value2); // 运行时输出:Hello 42

映射类型示例

type Mutable<T> = {  
    -readonly [K in keyof T]: T[K];  
    //以下写法也正确
    //-readonly [K in keyof T]: (a: T[K]) => void;
};  
  
type Point = {  
    readonly x: number;  
    readonly y: number;  
};  
  
const p: Mutable<Point> = {x: 10, y: 20};  
p.x = 15; // ✅ 可修改  
  
console.log(p);

Template Union Type

模板联合类型让 字符串字面量的组合变得类型安全且可预测,是构建健壮前端/后端类型系统的重要工具。

type EventName = 'click' | 'hover' | 'focus';  
type EventType = 'on' | 'off';  
  
type HandlerName = `${EventType}${Capitalize<EventName>}`;  
  
  
// 使用  
const handler: HandlerName = "onClick"; // ✅  
console.log(handler);

type Size = 'small' | 'medium' | 'large';  
type Variant = 'primary' | 'secondary';  
  
type ClassName = `btn--${Size}-${Variant}`;  
  
  
const className: ClassName = "btn--large-secondary"; // ✅  
console.log(className)

何时用 interface?何时用 type

尽可能使用 interface,直到你需要 type 的功能。
interface 是“可扩展的对象契约”,type 是“任意类型的别名”
interface 描述“是什么”,用 type 表达“可以是哪些”

场景 推荐
定义对象/函数的公共 API(如组件 props、API 响应) interface(可扩展、更面向对象)
需要声明合并(如扩展现有库类型) interface
定义联合类型、元组、字符串字面量类型 type
使用映射类型、条件类型等高级特性 type
团队约定统一风格 保持一致即可
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐