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)

语言特性

readonly

浅层冻结:只保护当前层级的属性不被重新赋值,但不限制对其内部嵌套属性的操作

禁止重新赋值(Reassignment)

  • 允许:读取属性、修改属性内部的值(如 home.resident.age++)。
  • 禁止:将该属性指向一个全新的对象
interface Home {  
    readonly resident: { name: string; age: number };  
}  
  
function visitForBirthday(home: Home) {  
    console.log(`Happy birthday ${home.resident.name}!`);  
    home.resident.age++;  
}  
  
const myHome: Home = {  
    resident: {  
        name: "Alice",  
        age: 30  
    }  
};  
  
console.log(`修改前年龄: ${myHome.resident.age}`); // 输出: 30  
visitForBirthday(myHome);  
console.log(`修改后年龄: ${myHome.resident.age}`); // 输出: 31

类型兼容性: 结构化类型系统:

结构化类型系统 (Structural Typing): 只要两个对象的“形状”(即拥有的属性和方法)一致,就可以互相赋值,不管它们叫什么名字。

这与JAVA有着本质的区别,JAVA属于名义类型系统 (Nominal Typing),必须有明确的继承或实现关系

interface User {  
    name: string  
}  
  
interface Person {  
    name: string  
}  
  
const user: User = {name: "Alice"};  
const person: Person = user; // ✅ 允许,结构相同即可  
console.log(`${person.name}`)

类型兼容性: any

TypeScript 为了兼容 JavaScript 的动态性,提供了极高的灵活性

  • any 可以绕过所有类型检查,像普通 JS 一样操作。
  • TS 允许你将任何值赋给 any 类型,或者从 any 类型赋值给任何其他类型。 在JAVA中是禁止的,只能通过强制类型转换
let notSure: any = 4;  
notSure = "现在是字符串"; // ✅ 允许  
notSure = false; // ✅ 允许  
  
// 赋值给其他类型  
let str: string = notSure; // ✅ 允许  
console.log(str)  

// 甚至可以调用不存在的方法,编译器不报错(运行时可能报错)  
notSure.someRandomMethod(); // ✅ 编译通过

联合类型 (Union Types) 与 字面量类型

  • 联合类型:你可以定义一个变量是 string | number,或者更复杂的 SuccessResult | ErrorResult
  • 字面量类型:你可以定义类型为具体的值,比如 type Direction = "left" | "right"。这在 Java 中通常需要用 enum,但 TS 的写法更轻量,且支持数字和字符串字面量直接作为类型。
type Direction = "left" | "right" | "up";  
  
type ID = number | string;  
  
function move(dir: Direction, id: ID) {  
    console.log(`ID ${id}${dir} 移动`);  
}  
  
move("left", 123); // ✅ 正确  
// move("down", "abc"); // ❌ 错误:'down' 不在 Direction 中

可选属性与参数

虽然 Java 可以通过重载或包装类(如 Optional<T>)实现类似效果,但 TS 是原生支持的语法糖。

  • 接口可选属性:在接口定义中,你可以标记某些属性为可选(xPos?: number),这意味着对象可以不包含这个字段。Java 的类字段通常都是固定的。
  • 参数属性:TS 允许在构造函数参数上直接使用 public/private 修饰符,自动生成类属性,减少了样板代码。
interface PaintOptions {  
    color: string;  
    xPos?: number; // 可选  
    yPos?: number; // 可选  
}  
  
function paint(opt: PaintOptions) {  
    // 解构时提供默认值  
    const {xPos = 0, yPos = 0} = opt;  
    console.log(`绘制在 (${xPos}, ${yPos})`);  
}  
  
// 调用时可以只传必要参数  
paint({color: "red"}); // ✅  
paint({color: "blue", xPos: 10}); // ✅

基于原型的继承

基本用法

TS 在语法上支持 class,但其底层是基于 JavaScript 的原型链,可以在运行时动态地给对象添加方法或属性(虽然不推荐),或者修改原型

class Animal {  
    name: string;  
  
    constructor(name: string) {  
        this.name = name;  
    }  
  
    speak() {  
        console.log(`${this.name} makes a sound.`);  
    }  
}  
  
// 不添加如下接口定义,会在静态检查时报: TS2339: Property bark does not exist on type Animal// interface Animal {  
//     bark(): void;  
// }  
  
const dog = new Animal("Dog");  
  
// 在运行时动态给所有 Animal 实例添加方法!  
Animal.prototype.bark = function () {  
    console.log(`${this.name} barks!`);  
};  
  
dog.speak(); // 输出: Dog makes a sound.  
dog.bark();  // 输出: Dog barks! (方法是运行时添加的)  
  
// 甚至可以动态修改已有方法  
Animal.prototype.speak = function () {  
    console.log(`${this.name} makes a LOUD sound!`);  
};  
  
dog.speak(); // 输出: Dog makes a LOUD sound! (方法已被运行时修改)

MixMin

// 定义一个“行为”对象  
const Flyable = {  
    fly() {  
        console.log(`${this.name} is flying!`);  
    }  
};  
  
const Swimmable = {  
    swim() {  
        console.log(`${this.name} is swimming!`);  
    }  
};  
  
class Bird {  
    name: string;  
  
    constructor(name: string) {  
        this.name = name;  
    }  
  
    speak() {  
        console.log(`${this.name} chirps.`);  
    }  
}  
  
// 将 Flyable 的能力“混合”到 Bird 的原型上  
Object.assign(Bird.prototype, Flyable);  
  
const duck = new Bird("Duck");  
duck.speak(); // 输出: Duck chirps.  
duck.fly();   // 输出: Duck is flying!  
  
// 甚至可以动态混合多个行为  
Object.assign(duck, Swimmable); // 注意:这里是混入到实例上  
duck.swim(); // 输出: Duck is swimming!

装饰器的元编程能力

虽然两者都有装饰器/注解,但 TS 的装饰器是语言级的语法特性,可以直接修改类的行为;而 Java 的注解更多是作为元数据标记,通常配合 APT 【Annotation Processing Tool,编译时根据注解生成代码】或反射在运行时处理,不能直接改变类的逻辑

基本用法

// 装饰器函数:给类添加一个属性  
function AddProperty(constructor: Function) {  
    constructor.prototype.newProp = "我是新增的属性";  
}  
  
@AddProperty  
class MyClass {  
}  
  
const obj: any = new MyClass();  
console.log(obj.newProp); // ✅ 输出: 我是新增的属性

ClassMethodDecoratorContext

function Log(value: Function, context: ClassMethodDecoratorContext) {  
    // context 包含了关于被装饰方法的信息  
    const methodName = String(context.name);  
  
    // 返回一个新的函数,这个函数将作为被装饰方法的新实现  
    return function (...args: any[]) {  
        console.log(`🚀 开始执行方法: ${methodName}`);  
        console.log(`📝 参数: ${JSON.stringify(args)}`);  
  
        try {  
            // @ts-ignore: 调用原始方法。这里 ts-ignore 是因为 TypeScript 5.x 对于这种模式的类型推断还不够完善。  
            const result = value.apply(this, args);  
            console.log(`✅ 方法 ${methodName} 执行成功`);  
            console.log(`📤 返回值: ${JSON.stringify(result)}`);  
            return result;  
        } catch (error) {  
            console.log(`❌ 方法 ${methodName} 执行出错:`, error);  
            throw error;  
        }  
    };  
}  
  
// 测试类  
class Calculator {  
    @Log // 现在这样写就对了  
    add(a: number, b: number): number {  
        return a + b;  
    }  
}  
  
const calc = new Calculator();  
calc.add(2, 3);

传递对象参数的方式

  • 匿名对象字面量:最常用,适合一次性传递数据。
  • 已存在的对象变量:当你已经有一个对象,并希望将其传递给函数时。
  • 类实例:当类实现了所需接口时,实例可以传递。
  • 解构接收:当你只关心对象中的部分属性时,可以让函数签名更清晰。
  • Rest 操作符:当你需要将对象的部分属性单独处理,其余属性作为一个整体处理时。
// 定义一个接口来描述对象的结构  
class User {  
    name: string;  
    age: number;  
    email?: string; // 可选属性  
  
    constructor(name: string, age: number) {  
        this.name = name;  
        this.age = age;  
    }  
}  
  
// 函数接收一个 User 类型的对象  
function greet(user: User) {  
    console.log(`Hello, ${user.name}! You are ${user.age} years old.`);  
    if (user.email) {  
        console.log(`Your email is ${user.email}.`);  
    }  
}  
  
// 传递匿名对象:直接传递一个对象字面量  
greet({  
    name: "Alice",  
    age: 30,  
    email: "alice@example.com"  
});  
  
const userA: User = {  
    name: "Bob",  
    age: 25,  
    email: "jasen@gmail.com"  
}  
  
// 传递已存在的对象变量  
greet(userA);  
  
// 传递类实例  
const userB: User = new User("张三", 34)  
greet(userB);  

//析构
function displayUserInfo({name, age, ...remainingInfo}: User) {  
    console.log(`name: ${name}, age: ${age},remainingInfo:${JSON.stringify(remainingInfo)}`);  
    // 注意:category 没有被解构,所以在函数内部无法直接访问  
}  
  
console.info(displayUserInfo(userA))

类型守卫

类型守卫的核心价值在于:将运行时的类型检查逻辑,转化为编译时的类型信息。无论你是处理复杂的业务状态、解析不可信的外部数据,还是重构大型联合类型,它都能让你的代码既灵活又安全

  • instanceof 守卫: if (date instanceof Date) {
  • typeof 守卫: if (typeof padding === "number") {
  • 自定义 Array.isArray 守卫:
  • 处理 any 或外部数据
interface Book {  
    title: string;  
    pages: number;  
}  
  
// 类型守卫:检查是否为 Book 数组  
function isBookArray(data: any): data is Book[] {  
    return Array.isArray(data) && data.every(item =>  
        typeof item.title === "string" && typeof item.pages === "number"  
    );  
}  
  
function isApiResponse(data: any): data is { success: boolean; data: User[] } {  
    return (  
        typeof data === "object" &&  
        data !== null &&  
        typeof data.success === "boolean" &&  
        Array.isArray(data.data)  
    );  
}
// 1. 定义类型 (编译时注解)  
interface User {  
    id: number;  
    name: string;  
    email: string;  
    role: 'user';  
}  
  
interface Admin {  
    id: number;  
    name: string;  
    permissions: string[];  
    role: 'admin';  
}  
  
// 类型别名,用于函数参数  
type Person = User | Admin;  
  
// 2. 类型守卫 (运行时检查)  
// 使用 'in' 操作符进行区分  
function isUser(person: any): person is User {  
    return 'email' in person && person.role === 'user';  
}  
  
function isAdmin(person: any): person is Admin {  
    return 'permissions' in person && person.role === 'admin';  
}  
  
// 3. 核心业务逻辑  
// 函数参数使用编译时类型注解  
function processUserData(input: Person): void {  
    // 运行时类型守卫检查  
    if (isUser(input)) {  
        // TypeScript 现在知道 input 是 User 类型  
        console.log(`处理用户: ${input.name}, 邮箱: ${input.email}`);  
    } else if (isAdmin(input)) {  
        // TypeScript 现在知道 input 是 Admin 类型  
        console.log(`处理管理员: ${input.name}, 权限: ${input.permissions.join(', ')}`);  
    } else {  
        // 穷尽性检查 (Exhaustive Checking)        // 如果未来添加了新类型而没有更新守卫,这里会报错  
        const _exhaustiveCheck: never = input;  
        throw new Error(`未知的用户类型: ${_exhaustiveCheck}`);  
    }  
}  
  
// --- 模拟运行时数据 (可能来自 API) ---  
// 模拟正确的数据  
const userData: any = {id: 1, name: "Alice", email: "alice@example.com", role: "user"};  
const adminData: any = {id: 2, name: "Bob", permissions: ["read", "write"], role: "admin"};  
  
// 模拟错误的数据 (缺少字段)  
const badData: any = {name: "Hacker", role: "user"};  
  
// 执行  
try {  
    processUserData(userData);   // ✅ 成功  
    processUserData(adminData);  // ✅ 成功  
    processUserData(badData);    // ❌ 运行时报错,类型守卫拦截  
} catch (error) {  
    console.error("数据处理失败:", error.message);  
}

泛型示例

// 成功时的结构  
type Ok<T> = { ok: true; value: T };  
// 失败时的结构  
type Err<E = Error> = { ok: false; error: E };  
  
// 联合类型  
type Result<T, E = Error> = Ok<T> | Err<E>;  
  
// 辅助函数:创建成功的结果  
const ok = <T>(value: T): Result<T> => ({ok: true, value});  
  
// 辅助函数:创建失败的结果  
const err = <E>(error: E): Result<never, E> => ({ok: false, error});  
  
// 包装异步函数,捕获异常并返回 Result
async function safe<T, E = Error>(  
    promise: Promise<T>  
): Promise<Result<T, E>> {  
    try {  
        const value = await promise;  
        return ok(value);  
    } catch (error) {  
        return err(error as E);  
    }  
}  
  
// 模拟一个可能失败的异步操作  
async function riskyOperation(): Promise<number> {  
    if (Math.random() < 0.5) throw new Error("Something went wrong");  
    return 42;  
}  
  
async function main() {  
    // 使用 safe 包装  
    const result = await safe<number>(riskyOperation());  
  
    // 显式处理两种情况  
    if (result.ok) {  
        console.log("Success:", result.value); // 类型自动推断为 number    
    } else {  
        console.error("Error:", result.error.message); // 类型自动推断为 Error    
    }  
}  
  
main()

private / #

private 是 TypeScript 层面的编译时检查,而 # 是 JavaScript 层面的运行时强私有。

特性 private # (私有字段)
作用时机 编译时 (TypeScript) 运行时 (JavaScript)
运行时安全性 低 (可通过反射/断言绕过) 高 (硬私有,无法绕过)
继承访问 子类不可访问 子类不可访问
跨实例访问 允许 允许 (在类方法内)
同名处理 子类同名会报错/覆盖 子类同名视为独立字段

this

this is not magically bound to the class instance; it is determined dynamically by the call site (how the function is invoked), not where it is defined.

Call Style Example this Context Notes
Method Call obj.method() obj The “happy path”
Function Call const fn = obj.method; fn(); undefined / window The Pitfall: Context is lost
Arrow Function method = () => {} Class Instance this is captured at definition
Bound Function method.bind(obj) Specified Object Explicitly sets the context

never

在 TypeScript 中,never 类型是一个非常特殊且强大的概念。简单来说,它表示**“永远不会发生”的值的类型**。

你可以把它理解为 TypeScript 类型系统的“黑洞”或“终点”:没有任何值可以属于 never 类型,它只存在于类型推导的逻辑中。

使用的场景:

  • 函数永远不会正常返回: 抛出异常 或 死循环,函数永远不会结束
  • 类型缩小中的“不可能”情况,如type Status = "loading" | "success"; 当 TypeScript 通过逻辑判断(如 ifswitch)排除了所有可能性后,剩下的分支类型就会被收窄never

运行时与编译时特性

编译目标

  • TypeScript: 最终编译为 JavaScript,可以在浏览器、Node.js 等多种环境中运行。其类型信息在编译后被擦除,仅用于编译时检查。
  • Java: 编译为字节码(.class 文件),在 Java 虚拟机(JVM)上运行。类型信息在运行时部分保留(可通过反射获取)。

泛型擦除

  • TypeScript: 泛型同样在编译时被擦除,运行时没有泛型信息。
  • Java: 使用“类型擦除”(Type Erasure),运行时泛型信息会消失,原始类型信息被保留(例如 List<String> 在运行时变为 List<Object>)。

异常处理

  • TypeScript: 无受检异常 (Unchecked Exception)。所有异常(如 Error)都是运行时抛出的,编译器不要求你显式声明或捕获它们,与 JavaScript 保持一致。
  • Java: 有受检异常 (Checked Exception)。编译器强制要求方法要么用 throws 声明可能抛出的受检异常,要么在方法体内用 try-catch 捕获。这有助于在编译期发现潜在的错误。

模块系统

  • TypeScript: 完全兼容 ES6 模块 (import/export),也支持 CommonJS、AMD 等多种模块格式,编译时可根据 tsconfig.json 配置输出不同格式。
  • Java: 从 Java 9 开始引入了模块系统(JPMS - Java Platform Module System),通过 module-info.java 文件定义模块及其依赖关系。在此之前主要依赖包(package)和 JAR 文件。

对象创建与 new 操作符

  • TypeScript: 除了 new ClassName() 外,还可以通过对象字面量 {}Object.create() 等多种方式创建对象,非常灵活。
  • Java: 通常必须使用 new 关键字和构造函数来创建对象实例。创建对象的方式相对固定。

成员可见性

  • TypeScript: 默认情况下,类成员(属性、方法)是 public。支持 public, private, protected 修饰符,但这些在编译后会被移除(除非使用 --stripInternal 等工具),主要在编译时起作用。
  • Java: 默认包级私有(package-private)。拥有严格的 private/protected/public 访问控制,这些修饰符在运行时有效,由 JVM 强制执行。

空值处理

  • TypeScript: 提供 nullundefined 两个值。通过 strictNullChecks 编译选项,可以强制类型系统更严格地处理空值,避免 NullPointerException 类似的错误。
  • Java: 引入了 Optional<T> 类来帮助处理可能为空的值,鼓励开发者显式地表示和处理空值情况,以减少 NullPointerException

类型推断能力

  • TypeScript: 拥有非常强大的类型推断能力,可以在很多情况下自动推断变量、函数返回值等的类型,减少显式标注的工作量。
  • Java: 从 Java 10 引入了 var 关键字,可以进行局部变量类型推断,但其推断能力相比 TypeScript 较弱。

函数是一等公民

  • TypeScript: 函数是对象,可以赋值给变量、作为参数传递、作为返回值返回,支持高阶函数、闭包等函数式编程特性。
  • Java: 从 Java 8 引入了 Lambda 表达式和函数式接口(Functional Interface),大大增强了对函数式编程的支持,但函数本身不是一等公民,Lambda 表达式是函数式接口的实例。

混入 (Mixins) 模式

  • TypeScript: 通过对象原型和 Object.assign 等方式,可以非常方便地实现 Mixin 模式,将多个对象的能力组合到一个类上。
  • Java: 传统上不支持多重继承,难以直接实现 Mixin。通常需要使用接口(并结合默认方法)或组合模式来模拟 Mixin 的效果。

异步编程模型

  • TypeScript: 支持 async/awaitPromise 等现代异步编程模型,与 JavaScript 生态无缝集成。
  • Java: 提供了 CompletableFutureFuture 等工具,以及 Project Loom(虚拟线程)等新特性来简化异步和并发编程,但模型与 JS/TS 的事件循环机制不同。

生态与应用场景

  • TypeScript: 主导前端开发(Web、移动App),也用于 Node.js 后端开发、桌面应用(Electron)等,生态围绕 JavaScript 生态。
  • Java: 主导企业级后端开发、Android 原生应用、大数据(Hadoop, Spark)、桌面应用(Swing, JavaFX)等,生态庞大且成熟,侧重服务器端。

常用命令

npm list -g
npm --version
tsc --version

npm install -g typescript
npm install -g create-vit

tsc helloworld.ts

附录

typescript 5.x与 java 17.x的区别

  • 因为TypeScript本质上是 JavaScript 的超集, 用强大的静态类型检查(编译期)来预防错误,而不是用受检异常(编译期+运行期)来强制处理错误
维度 TypeScript 5.x Java 17.x
本质 JavaScript 的超集,最终编译为 JS 独立的编程语言,编译为字节码(Bytecode)
运行环境 浏览器、Node.js、移动端 Java 虚拟机(JVM),跨平台
主要用途 前端开发(Web/App)、Node.js 后端 企业级后端服务、Android 应用、大数据
类型系统机制 结构化类型系统 (Structural Typing) 名义类型系统 (Nominal Typing)
编译/构建 编译速度快,通常配合 Webpack/Vite 进行打包 使用 Maven/Gradle 构建,编译过程包含严格的语法检查
异常处理 无受检异常 (Checked Exception),异常处理是可选的 有受检异常,必须显式 try-catchthrows 声明
访问修饰符 默认是 public,成员可见性控制相对宽松 默认包级私有,拥有严格的 private/protected/public 控制
生态工具 依赖 npm/yarn,配置通过 tsconfig.json 依赖 Maven 中央仓库,配置通过 pom.xml

结构化类型/名义类型系统

Structural Typing(结构化类型系统)和 Nominal Typing(名义类型系统)是两种根本不同的类型兼容性判断规则。

维度 Structural Typing (TypeScript) Nominal Typing (Java)
判断依据 对象的结构(属性和方法)是否匹配 类型名称和继承关系是否匹配
关键词 “鸭子类型” (If it walks like a duck and quacks like a duck, it’s a duck.) “类/接口声明” (Must explicitly extend/implement)
灵活性 高,只要有相同结构即可 低,必须有明确的“血缘”关系
示例场景 一个对象有 nameage 属性,它就能赋值给需要 {name: string, age: number} 的地方 一个类必须 implements 接口或 extends 父类,才能赋值给对应的接口/父类变量

JAVA开发者速查表

场景 Java 思维 (❌) TypeScript 思维 (✅)
定义数据结构 定义 Class,写 getter/setter 使用 interfacetype,直接访问属性
创建对象 大量使用 new 优先使用对象字面量 {} 或工厂函数
类型检查 依赖运行时的 instanceof / 反射 依赖编译时的类型注解,运行时用类型守卫
错误处理 try-catch 捕获受检异常 Promise.catch()try-catch (异步),多用 Result 模式或 Option
空值处理 随时可能 NullPointerException 使用 ?.??,开启 strictNullChecks
工具函数 写在 Utils 静态类中 直接导出纯函数 export function xxx
面向对象 深层次的类继承体系 组合优于继承,Mixin 模式,接口只是“形状”

误解或坑

误解 private

在 TypeScript 中,private 仅在编译时有效
TS 的 private 修饰符主要靠约定。
在运行时通过反射或直接访问属性,依然可能访问到

理解“编译时”与“运行时”的鸿沟

TypeScript 的类型系统在编译成 JavaScript 后会完全消失(Type Erasure)

nullundefined 的处理

Java 有 NullPointerException,TypeScript 有 strictNullChecks

  • 建议务必开启 strictNullChecks: true。这是 TypeScript 最有价值的配置之一。
  • 新技能:学习使用可选链 (Optional Chaining) ?.空值合并 (Nullish Coalescing) ??
    • Java 写法:if (user != null && user.getAddress() != null) { ... }
    • TS 写法:user?.getAddress()user?.address?.street

第三方库的类型定义

不是所有 npm 包都自带类型。

  • 解法:学会使用 @types/xxx。例如,使用 lodash,通常需要安装 @types/lodash
  • 缺失类型时:如果库没有类型定义,不要急着用 any,可以尝试在 types 目录下自己写一个简单的声明文件 declare module 'xxx'

工程与工具链:告别“约定优于配置”

忽视 tsconfig.json
Java 项目通常结构固定(src/main/java)。TypeScript 项目非常灵活,但这也意味着你需要配置。

  • 关键配置
    • strict: 必须设为 true,开启所有严格检查。
    • target: 指定编译后的 JS 版本(如 ES2016)。
    • module: 指定模块系统(如 CommonJSESNext)。
    • baseUrlpaths: 配置路径别名(如 @/components),避免满屏的 ../../../../
Logo

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

更多推荐