TypeScript 从入门到放弃

目录

什么是 TypeScript

配置 TypeScript 环境

执行 .ts 文件

TypeScript 基础语法

声明变量

let 关键字

const 关键字

数据类型

控制流

条件语句

循环语句

函数

普通函数

函数表达式

可选参数

默认值参数

剩余参数

箭头函数 

箭头回调函数

面向对象编程

访问修饰符

类  构造函数简写

继承类

抽象类

静态属性、静态方法

只读属性

接口

接口应用到变量

接口函数类型

实现接口

泛型

泛型函数

泛型接口

泛型类

约束泛型类型


什么是 TypeScript

TypeScript(简称 TS)是一种由微软开发的开源编程语言,它是 JavaScript 的超集,在 JavaScript 的基础上增加了静态类型。TypeScript 可以编译为标准的 JavaScript 代码,在浏览器、Node.js 以及任何支持 JavaScript 的环境中运行。

配置 TypeScript 环境

使用 Node.js(需提前配置好 Node.js 环境)环境安装 TypeScript,安装命令如下:

# 本地安装在多人协作开发时不会出现版本不一致的问题
npm install --save-dev typescript

验证 TypeScript 是否安装成功

tsc -v


执行 .ts 文件

方式一: 不推荐

编译 .ts 文件为 .js 文件运行,使用 tsc <file>.ts 命令,会在同级目录下创建一个同名的.js文件。然后使用命令 node <file>.js 运行文件。

# tsc <file>.ts
tsc test.ts

# node <file>.js
node test.js

方式二:(使用 CommonJS 模块的推荐)

使用 ts-node 运行 .ts 文件。ts-node 是一个TypeScript 执行环境,不需要先编译 .ts 文件,ts-node 会在运行时自动把 .ts 文件编译为 .js 文件并执行。ts-node默认使用CommonJS模块,所以package.json 文件中不能有 "type": "module" 的配置,因为不兼容。ts-node 会使用你项目中的 tsconfig.json 配置文件(如果有的话)来决定编译选项。如果没有 tsconfig.json 文件,ts-node 会使用默认配置。(使用 tsc --init 命令创建 tsconfig.json 文件)。

安装 ts-node

npm install --save-dev ts-node

使用 ts-node 执行 .ts 文件

# ts-node <file>.ts
ts-node test.ts

方式三:(使用 ESM 模块的推荐)

安装 tsx

npm install -g tsx

 使用 tsx 执行 .ts 文件

# tsx  <file>.ts
npx tsx test.ts

什么是 tsconfig.json

tsconfig.json 是核心配置文件,用于指定 TypeScript 编译器(tsc)的行为,如 目标 JavaScript 版本、模块解析、严格类型检查、代码优化 等。


TypeScript 基础语法

声明变量

声明变量,使用关键字 let 和 const

let 关键字

let 用于声明可变(可重新赋值)的变量,它有以下特点:

  • 支持块级作用域(block scope)
  • 可以重新赋值
  • 不会变量提升(hoisting),声明之前访问会报错
  • 不会污染全局作用域

语法:

let 变量名称: 数据类型 = 值

示例:                   //  let 声明变量

let userName: string = "Alice";  // 变量,可修改
userName = "Tom";
console.log(userName);

// let userName: string | null = null;  // 赋值 null
// let userName: string = "";           // 赋值 "" 字符串

执行 .ts 文件查看效果        // 文中后面省略此步骤,以减少重复的内容

ts-node test.ts
tsx test.ts

const 关键字

const 用于声明常量(不可变的变量),一旦赋值就不能修改。

  • 支持块级作用域(block scope)
  • 必须在声明时赋值(不能先声明后赋值)
  • 不能重新赋值(但对象、数组的内容可以修改)
  • 不会变量提升(hoisting)

语法:

const 常量名称: 数据类型 = 值

示例:                   //  const 声明常量

const age: number = 30; // 常量,不能修改
console.log(age);

数据类型

基本数据类型:number、string、boolean、null、undefined、symbol、bigint。
复杂数据类型:array、tuple、enum、object。
特殊数据类型:any、unknown、void、never。
类型推导与联合类型:| 用于联合多个类型,type 用于定义类型别名。

示例:number,表示数字,包括整数和浮点小数。

let age: number = 25;
let pi: number = 3.14;

示例:string,表示字符串。

let name: string = "Tom";

示例:boolean,表示布尔值,只有 truefalse 两个值。

let isActive: boolean = true;
let isOnline: boolean = false;

示例:null,表示空值或无值,通常用于指示“没有值”。

let user: null = null;

示例:undefined,表示变量未赋值或未定义的值。

let notAssigned: undefined = undefined;

示例:symbol (ES6+),表示一个唯一的值,通常用于对象属性的键。

let sym: symbol = Symbol("description");

示例:bigint (ES11+),表示大整数(超过 Number 类型的范围)。

let largeNumber: bigint = 1234567890123456789012345678901234567890n;

示例:array,数组,存储多个元素,可以指定元素的类型。(包括增删查改操作)

// 同类型数组
let numbers: number[] = [1, 2, 3];

/** 添加元素 */
/* push() 用于向数组的末尾添加一个或多个元素 */
// 添加单个元素
numbers.push(4);
console.log(numbers);     // 输出: [1, 2, 3, 4]

// 添加多个元素
numbers.push(5, 6);
console.log(numbers); // 输出: [1, 2, 3, 4, 5, 6]

/* unshift() 用于向数组的开头添加一个或多个元素 */
numbers.unshift(0);
console.log(numbers); // 输出: [0, 1, 2, 3, 4, 5, 6]

/* splice() 指定下标位置插入元素 */
// 在索引 3 的位置插入元素 10,11
// numbers.splice(3, 0, 10, 11);
// console.log(numbers); // 输出: [0, 1, 2, 10, 11, 3, 4, 5, 6]

/** 删除元素 */
/* pop() 用于删除数组中的最后一个元素 */
let removedElementPop = numbers.pop();
console.log(removedElementPop); // 删除的元素 输出: 6
console.log(numbers); // 输出: [ 0, 1, 2, 3, 4, 5 ]

/* shift() 用于删除数组中的第一个元素 */
let removedElementShift = numbers.shift();
console.log(removedElementShift); // 删除的元素 输出: 0
console.log(numbers); // 输出: [ 1, 2, 3, 4, 5 ]

/* splice() 用于删除数组中的指定位置的一个或多个元素 */
// 删除从索引 2 开始的一个元素
let removedElements = numbers.splice(2, 1);
console.log(removedElements); // 输出: [3]
console.log(numbers); // 输出: [1, 2, 4, 5]

// 删除从索引 1 开始的两个元素
numbers.splice(1, 2);
console.log(numbers); // 输出: [1, 5]

/** 查找 */
// 查询指定索引的元素
console.log(numbers[1]);    // 输出:5

/* indexOf() 用于查找数组中某个元素的索引,如果元素不存在,返回 -1 */
let index = numbers.indexOf(5);
console.log(index); // 输出: 1

let indexNotFound = numbers.indexOf(10);
console.log(indexNotFound); // 输出: -1

/* includes() 用于检查数组中是否包含某个元素,返回 true 或 false */
let containsThree = numbers.includes(1);
console.log(containsThree); // 输出: true

let containsSix = numbers.includes(6);
console.log(containsSix); // 输出: false

/** 修改 */
// 使用元素索引修改元素
numbers[1] = 10;
console.log(numbers);   // 输出:[ 1, 10 ]

也可以使用 Array<type> 语法。(增删查改同上)

let fruits: Array<string> = ["apple", "banana", "cherry"];

示例:tuple,元组,允许你存储不同类型的元素,定义元素数量和类型。(包括增删查改操作)

let person: [string, number] = ["Alice", 25];  // 第一个是字符串,第二个是数字

// 添加新元素,使用 push 函数
person.push("Engineer");  // 可以推入新的元素,但会改变元组的长度
console.log(person);  // 输出: ["Alice", 25, "Engineer"]

// 删除最后一个元素
let removedElementPop = person.pop();
console.log(removedElementPop);  // 输出: Engineer
console.log(person);  // 输出: [ 'Alice', 25 ]

// 删除第一个元素
let removedElementShift = person.shift();
console.log(removedElementShift);  // 输出: "Alice"
console.log(person);  // 输出: [25]

// 查找元素的索引
let index = person.indexOf(25);
console.log(index);  // 输出: 0

let notFoundIndex = person.indexOf("Alice");
console.log(notFoundIndex);  // 输出: -1

// 检查元素是否存在
let hasAlice = person.includes("Alice");
console.log(hasAlice);  // 输出: false

// 修改元素,通过索引
person[0] = "Bob";
console.log(person);  // 输出: ["Bob", 25]

// 使用 splice 替换元素
person.splice(0, 1, "Charlie");  // 从索引 0 开始,删除 1 个元素,并插入 "Charlie"
console.log(person);  // 输出: ["Charlie"]

示例:enum,枚举类型,用于定义一组具名的常量。

enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}

let move: Direction = Direction.Up;

示例:object,对象类型,表示一组键值对,键(属性名)是字符串类型,值可以是任何类型。

// 定义对象
let person: { name: string; age: number } = { name: "Tom", age: 25 };

// 定义对象时,对象属性值可以不赋值,使用 ? 号
let person2: { name: string; age?: number } = { name: "Tom" };
console.log(person.age); // 输出: undefined(age 是可选的)

// 允许对象不预先定义属性
let person3: { name: string; age: number;[key: string]: any; } = { name: "Tom", age: 25 };
person3.gender = "male"; // 允许增加新属性
person3.aaa = 10
console.log(person3); // 输出: { name: 'Tom', age: 25, gender: 'male' }
// 对象添加属性
let person2: { name: string; age: number;[key: string]: any } = { name: "Tom", age: 25 };
// 现在可以动态添加属性
person2.gender = "male";
person2.city = "New York";
console.log(person2); // 输出: { name: 'Tom', age: 25, gender: 'male', city: 'New York' }
// 对象删除属性
let person: { name: string; age: number } = { name: "Tom", age: 25 };

// 使用类型转换让属性可选
let modifiablePerson: Partial<{ name: string; age: number }> = person;

delete modifiablePerson.age; // 允许删除
console.log(modifiablePerson); // 输出: { name: 'Tom' }
/** 对象查询 */
let person: { name: string; age: number } = { name: "Tom", age: 25 };

// 直接访问对象属性
console.log(person.name); // 输出: "Tom"
console.log(person.age); // 输出: 25

// 检查属性是否存在, 使用 in 关键字
console.log("name" in person); // 输出: true
console.log("gender" in person); // 输出: false

// 获取所有属性,使用 Object.keys()
console.log(Object.keys(person)); // 输出: ['name', 'age']
// 对象修改属性
let person: { name: string; age: number } = { name: "Tom", age: 25 };

person.name = "Alice"; 
person.age = 30;
console.log(person); // 输出: { name: 'Alice', age: 30 }

也可以使用 Record 来定义对象(相当于Map)。

let person: Record<string, string> = { name: "Tom", city: "Wonderland" };

示例:any,表示可以是任何类型,禁用了类型检查,适合动态类型的场景。

let something: any = 5;
something = "Hello";
something = true;

let mixedArray: any[] = ["Alice", 25, true, { name: "Bob" }, [1, 2, 3]];

示例:unknown,与 any 类似,但更加安全。unknown 类型的变量在使用之前必须进行类型检查。

let value: unknown = 10;
if (typeof value === "number") {
    console.log(value.toFixed(2));  // 只有类型确认后才可以使用
}

示例:void,通常用于函数返回值类型,表示没有返回值。

function sayHello(): void {
    console.log("Hello!");
}

示例:never,表示永远不会有返回值的类型。通常用于表示不可能发生的情况,例如函数抛出异常或死循环。

function throwError(message: string): never {
    throw new Error(message);
}

示例:Union Types 联合类型,可以将多个类型组合在一起,表示值可以是这些类型之一。

let value: string | number = "Hello";  // 可以是 string
value = 42;  // 也可以赋值为 number

示例:Type Alias 类型别名,可以为复杂类型创建别名。

// 给复杂的类型起个简便的名字
type StringOrNumber = string | number;
let someValue: StringOrNumber = "Hello";
someValue = 42;
/** 属性类型别名,相当于枚举,比枚举更轻量 */
// 相当于创建了一个新的类型 Animal,它是一个联合类型(Union Type),意味着变量只能取 "dog"、"cat" 或 "fish" 这三个值,取其他值会报错。
type Animal = "dog" | "cat" | "fish";

let pet: Animal;
pet = "cat";

/* 作为函数参数使用 */
function checkAnimal(animal: Animal) {
    console.log(`This is a ${animal}.`);
}
checkAnimal("cat");

/* 作为对象属性使用 */
type Pet = {
    name: string;
    type: Animal;  // 只能是 "dog" | "cat" | "fish"
};

const myPet: Pet = {
    name: "Buddy",
    type: "dog", // 正确
};

示例:类型推导,TypeScript 能够根据变量的初始值推导出类型。

let num = 10;  // TypeScript 推导出 num 类型为 number

控制流

条件语句

if

let score: number = 85;

if (score >= 90) {
    console.log("Grade: A");
} else if (score >= 70) {
    console.log("Grade: B");
} else {
    console.log("Grade: C");
}

switch

let day: number = 6;
let dayName: string;

switch (day) {
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    default:
        dayName = "Invalid day";
}
console.log(dayName);
// switch 和 联合类型 和 never 一起使用
/* 示例:调用函数时限制了只能传入指定的值 */
type Animal = "dog" | "cat" | "fish";

function checkAnimal(animal: Animal) {
    switch (animal) {
        case "dog":
            console.log("This is a dog.");
            break;
        case "cat":
            console.log("This is a cat.");
            break;
        case "fish":
            console.log("This is a fish.");
            break;
        default:
            const _exhaustiveCheck: never = animal;  // 确保 `switch` 处理了所有 `Animal` 值
            throw new Error(`Unknown animal: ${animal}`);
    }
}

// 正确调用
checkAnimal("dog");
checkAnimal("cat");
checkAnimal("fish");
// 错误调用,只能输入 Animal 中限定的值
// checkAnimal("rabbit");

三元运算符

let age: number = 20;
let status: string = age >= 18 ? "Adult" : "Minor";
console.log(status); // 输出: "Adult"

循环语句

for

for (let i = 0; i < 5; i++) {
    console.log(i); // 输出: 0, 1, 2, 3, 4
}

// 遍历数组
let numbers: number[] = [10, 20, 30, 40];
for (let i = 0; i < numbers.length; i++) {
    console.log(numbers[i]); // 输出: 10, 20, 30, 40
}

for ... of

// 遍历数组
let numbers: number[] = [10, 20, 30, 40];

for (let num of numbers) {
    console.log(num); // 输出: 10, 20, 30, 40
}

for ... in

// 遍历对象的 key 
let person = { name: "Tom", age: 25 };

for (let key in person) {
    console.log(key); // 输出: "name", "age"
}

while

let count: number = 0;

while (count < 5) {
    console.log(count); // 输出: 0, 1, 2, 3, 4
    count++;
}

do ... while

let count: number = 0;

do {
    console.log(count); // 输出: 0, 1, 2, 3, 4
    count++;
} while (count < 5);

break

// 终止当前循环,跳出循环体
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break; // 当 i 为 5 时,终止循环
    }
    console.log(i); // 输出: 0, 1, 2, 3, 4
}

continue

// 跳过当前循环的剩余部分,进入下一次循环
for (let i = 0; i < 10; i++) {
    if (i % 2 === 0) {
        continue; // 跳过偶数,输出奇数
    }
    console.log(i); // 输出: 1, 3, 5, 7, 9
}

函数

普通函数

语法:

function 函数名(参数名1: 参数类型, 参数名n: 参数类型): 返回类型 {

        ...函数体

}

示例:

// 定义函数
function greet(name: string): string {
    return `Hello, ${name}!`;
}

// 调用函数
const userName = greet("Tom");
console.log(userName);  // 输出:Hello, Tom!

函数表达式

语法:                        // 变量中存储的是一个匿名函数

const 变量名 = function (参数名: 参数类型): 返回类型 {

        ...函数体

        return 返回值

}

示例:

// 定义函数
const greet = function (name: string): string {
    return `Hello, ${name}!`;
};

// 调用函数
const userName = greet("Tom");
console.log(userName);  // 输出:Hello, Tom!

可选参数

可选参数是,调用函数时,参数实参可传可不传,使用 ? 号标记参数是可选参数。

语法:

function 函数名(参数名?: 参数类型): 返回类型 {

        ...函数体

}

示例:

// 定义函数
function greet(name: string, age?: number): void {
    console.log(`name = ${name}, age = ${age}`);
}

// 调用函数
greet("Tom");
greet("Tom", 18);

默认值参数

为函数的参数指定默认值,如果没有传递该参数,则使用默认值。

语法:

function 函数名(参数名: 参数类型=默认值): 返回类型 {

        ...函数体

}

示例:

// 定义函数
function greet(name: string, age: number = 18): string {
    return `Hello, ${name}. You are ${age} years old.`;
}

// 调用函数
console.log(greet("Tom"));

剩余参数

剩余参数使用 ... 表示,可以收集函数传入的多个参数,并将它们作为数组处理。

语法:

function 函数名(...参数名: 参数类型[]): 返回类型{

        ...函数体

}

示例:

// 定义函数
function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}

// 调用函数
console.log(sum(1, 2, 3));
// 定义接收任意类型的剩余参数函数
function logData(...data: any[]): void {
    console.log(data);
}

// 调用函数
logData("Alice", 25, true, { city: "Wonderland" });

箭头函数 

箭头函数是 ES6(ECMAScript 2015)引入的一个新特性,目的是简化函数的语法,特别是在回调函数和匿名函数中。箭头函数通过 => 来定义,并且有一些与普通函数不同的特性,最显著的就是它不会绑定自己的 this,它会继承外部作用域的 this,这使得它在一些特定场景下更加简洁和方便。(对应 Java 语言 Lambda 表达式)

语法:        // 普通语法

const 变量名 = (参数名: 参数类型): 返回类型 => {

        // 函数体

}

示例:

// 定义函数
const greet = (name: string): string => {
    return `Hello, ${name}!`;
};

// 调用函数
console.log(greet("Alice"));  // 输出: "Hello, Alice!"

语法:        // 当函数体只有一行时,可以省略 {} 花括号和 return。

const 变量名 = (参数名: 参数类型): 返回类型 => 函数体;

示例:

const greet = (name: string): string => `Hello, ${name}!`;

console.log(greet("Bob"));  // 输出: "Hello, Bob!"

语法:        // 当无参数时,() 也要带上不能省略。

const 变量名 = (): 返回类型 => {

        // 函数体

}

示例:

const greet = (): string => "Hello, World!";

console.log(greet());  // 输出: "Hello, World!"

箭头回调函数

语法:        // 箭头回调函数和箭头函数语法上有区别。

区别:

  • 箭头函数:

        const 变量名 = (参数名: 参数类型): 返回类型 => {

                // 函数体

        }

  • 箭头回调函数:

        // 带默认函数体

        const 函数名: (参数名: 参数类型) => 返回类型 = (默认回调实参) => {

                // 默认函数体

        }

        // 不带默认函数体(又叫普通函数类型)

        let 函数名: (参数名: 参数类型) => 返回类型;

示例:        // 定义不带默认函数体的回调函数,也叫普通函数类型,作用是确保函数的输入和输出符合预期。

// 定义函数类型
let myFunc: (a: number, b: number) => number;
// 定义的函数,符合函数类型
myFunc = (x, y) => x + y;
// 定义的函数,不符合函数类型
// myFunc = (x, y) => "hello";
console.log(myFunc(5, 3)); // 输出 8

示例:        // 定义带默认函数体的回调函数

// 定义带默认函数体的
const callback: (data: string) => string = (data) => {
    console.log(data);  // 输出传入的参数
    return "Tom"
};

// 调用函数
const cb = callback("Alice")    // 输出:Alice
console.log(cb);                // 输出:Tom

示例:        // 定义普通方法参数带回调函数(就是函数做为参数),回调函数不带默认函数体。

// 定义普通方法的参数是个回调函数
function fetchData(callback: (data: string) => void) {
    const data = "Hello, World!";
    callback(data);
}

// 调用函数
fetchData(function (data) {
    console.log(data);     // 输出:Hello, World!
});

示例:        // 定义普通方法参数带回调函数(就是函数做为参数),回调函数带默认函数体。

// 定义普通方法的参数是个回调函数
function fetchData(callback: (data: string) => void = (data) => {
    console.log("a = " + data);
}) {
    const data = "Hello, World!";
    callback(data);
}

// 调用函数,不自定义函数体,就使用默认函数体
fetchData();                        // 输出:a = Hello, World!

// 调用函数,自定义函数体,默认函数就不执行了
fetchData(function (data) {
    console.log("b = " + data);     // 输出:b = Hello, World!
});

面向对象编程

访问修饰符

修饰符 作用
public 任何地方可访问,默认
private 仅类内部可访问
protected 类及子类可访问

语法:

// 定义类

class 类名{

        修饰符 属性名1: 数据类型;

        属性名n: 数据类型;

        

        // 构造函数

        constructor(属性名1: 数据类型, 属性名n: 数据类型) {

                this.属性名1 = 属性名1;

                this.属性名n = 属性名n;

        }

        

        // 普通函数

        函数名(参数名: 数据类型): 返回类型 {

                // 函数体

        }

}

        

// 实例化类

const 对象名 = new 类名(构造函数实参);

// 调用类普通函数

对象名.函数名(函数实参);

示例:

class Person {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    greet(): void {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

const person = new Person("Alice", 25);
person.greet();

类  构造函数简写

TypeScript 允许在构造函数的参数前加修饰符,自动创建并初始化类属性。

语法:

// 定义类

class 类名{

        // 构造函数简写,属性定义写在了构造函数的参数里

        constructor(修饰符 属性名1: 数据类型, 修饰符 属性名n: 数据类型) {}

        // 普通函数

        函数名(参数名: 数据类型): 返回类型 {

                // 函数体

        }

}

// 实例化类

const 对象名 = new 类名(构造函数实参);

// 调用类普通函数

对象名.函数名(函数实参);

示例:

class Car {
    constructor(public brand: string, private year: number) { }

    getCarInfo(): string {
        return `Brand: ${this.brand}, Year: ${this.year}`;
    }
}

const car = new Car("Toyota", 2022);
console.log(car.brand);

继承类

使用 extends 关键字实现继承

示例:

// 父类
class Animal {
    public name: string; // 公开访问
    private age: number; // 私有访问
    protected species: string; // 受保护访问

    constructor(name: string, age: number, species: string) {
        this.name = name;
        this.age = age;
        this.species = species;
    }

    public getInfo(): string {
        return `This is a ${this.species} named ${this.name}.`;
    }
}

const animal = new Animal("Leo", 5, "Lion");
console.log(animal.name);

// 继承,子类继承父类
class Dog extends Animal {
    constructor(name: string, age: number, species: string, public breed: string) {
        super(name, age, species);
    }

    bark(): void {
        console.log(`${this.name} is barking!`);
    }
}

const dog = new Dog("Buddy", 3, "Dog", "Golden Retriever");
dog.bark();
console.log(dog.getInfo());

抽象类

抽象类不能被实例化,只能被继承,关键字 abstract。

示例:

// 定义抽象类
abstract class Shape {
    abstract getArea(): number; // 抽象方法, 需要子类实现

    printArea(): void {
        console.log(`The area is ${this.getArea()}`);
    }
}

// 实现类,子类继承抽象类
class Circle extends Shape {
    constructor(private radius: number) {
        super();
    }

    getArea(): number {
        return Math.PI * this.radius * this.radius;
    }
}

const circle = new Circle(5);
circle.printArea(); // 输出: The area is 78.53981633974483

静态属性、静态方法

使用 static 关键字定义静态属性和方法,不需要实例化类即可访问。

示例:

class MathUtils {
    static PI: number = 3.14159;

    static circleArea(radius: number): number {
        return this.PI * radius * radius;
    }
}

console.log(MathUtils.PI); // 可以直接访问静态属性
console.log(MathUtils.circleArea(5)); // 可以直接调用静态方法

只读属性

使用 readonly 关键字使属性值不可修改。

示例:

class Immutable {
    readonly id: number;

    constructor(id: number) {
        this.id = id;
    }
}

const obj = new Immutable(101);
console.log(obj.id);    // 允许读取
// obj.id = 202;        // 错误: 只读属性不可修改

接口

TypeScript 允许类实现接口,用于强制类遵守约定的结构,关键字 interface 。

语法:

interface 接口名 {

        属性名1: 数据类型;

        属性名n?: 数据类型;                // 可选属性

        

        函数名(参数名称: 数据类型): 返回类型;

}

示例:

// 定义接口
interface Flyable {
    name: string;         // 接口属性
    fly(): void;          // 接口函数
}

接口应用到变量

接口应用到变量:表示你要定义一个对象,对象里面的字段必须要符合定义的接口结构。不能出现多一个少一个字段之类的情况。

示例:

// 定义一个接口
interface Person {
    name: string;
    age: number;
}

/* 接口应用到变量 */
// 定义一个对象,限制对象字段要符合接口结构
const user: Person = {
    name: "Tom",
    age: 25
    // , email:"[email protected]" // 错误,不能出现接口中没定义过的属性
}; 

接口函数类型

接口函数类型指的是在 TypeScript 中使用接口来定义函数的类型,用于限制你定义的函数的参数和返回值符合接口中的函数类型定义的结构。

// 定义一个接口
interface MathFunc {
    (x: number, y: number): number; // 函数类型 (只能定义一个)
}

// 定义一个函数,要遵守上面的接口函数类型
const add1: MathFunc = (a, b) => a + b;
console.log(add1(10, 20)); // 输出 30

// 定义第二个函数,要遵守上面的接口函数类型
const add2: MathFunc = (a, b) => a + b;
console.log(add2(56, 20)); // 输出 76

实现接口

示例:

// 定义接口
interface Flyable {
    // 定义函数,需要子类实现
    fly(): void;
}

// 定义类,实现接口中的函数
class Bird implements Flyable {
    fly(): void {
        console.log("The bird is flying.");
    }
}

// 实例化类
const bird = new Bird();
// 调用实现方法
bird.fly();

泛型

TypeScript 泛型(Generics)用于创建可重用、可扩展的代码,使函数、类或接口可以处理不同类型的数据,同时保持类型安全。

泛型函数

泛型函数使用 <T> 作为占位符,表示函数可以接受不同类型的参数。

语法:

// 定义

function 函数名<T>(参数名: T): T {

        // 函数体

}

// 调用

函数名<任意数据类型>(实参);

函数名(实参);                        // 根据实参进行类型推导

示例:

// 定义泛型函数,调用时可以接收任意类型的参数
function identity<T>(value: T): T {
    return value;
}

// 调用时可以参数任意类型的实参,T 是一个类型变量,在调用 identity 时,可以指定具体的类型(如 number、string 等)
identity<number>(123);
identity<string>("Hello");
identity<boolean>(true);
// TypeScript 可以推断类型,因此调用时可以省略 <T>
identity(42); // 推导出 T 为 number
identity("Hello"); // 推导出 T 为 string


泛型接口

语法:

interface 接口名<T> {

        属性名: T;

}

示例:                // 泛型接口应用到变量

// 定义一个泛型接口
interface Box<T> {
    value: T;
}

// 接口应用到变量
const numberBox: Box<number> = { value: 100 };
const stringBox: Box<string> = { value: "Hello" };

示例:                // 泛型接口应用到函数

// 定义一个泛型接口
interface IdentityFunc<T> {
    (arg: T): T;
}

// 定义一个函数,函数类型是泛型接口,可以接受任意类型的参数
const identity: IdentityFunc<number> = (x) => x + 2;
console.log(identity(44)); // 输出: 46

// 定义第二个函数,函数类型是泛型接口,可以接受任意类型的参数
const stringIdentity: IdentityFunc<string> = (x) => x + " World";
console.log(stringIdentity("Hello")); // 输出: Hello World

泛型类

在 TypeScript 中,泛型类(Generic Class)允许我们定义可以处理多种数据类型的类,而不必为每种类型单独编写代码。这样可以提高代码的复用性和类型安全性

语法:

class 类名<T> {

        private 属性名: T; 

        // 省略构造和getset

}

示例:        // 一个泛型的类

// 创建一个泛型类
class Box<T> {
    private value: T;  // 使用泛型 T 作为成员变量的类型

    constructor(value: T) {
        this.value = value;
    }

    getValue(): T {
        return this.value;
    }

    setValue(value: T): void {
        this.value = value;
    }
}

// 创建一个存储 number 类型的 Box
const numberBox = new Box<number>(100);
console.log(numberBox.getValue()); // 输出: 100

// 创建一个存储 string 类型的 Box
const stringBox = new Box<string>("Hello");
console.log(stringBox.getValue()); // 输出: Hello

示例:        // 多个泛型的类

class Pair<K, V> {
    private key: K;
    private value: V;

    constructor(key: K, value: V) {
        this.key = key;
        this.value = value;
    }

    getKey(): K {
        return this.key;
    }

    getValue(): V {
        return this.value;
    }
}

// 使用不同的类型创建 Pair 实例
const pair1 = new Pair<number, string>(1, "One");
console.log(pair1.getKey(), pair1.getValue()); // 输出: 1 One

const pair2 = new Pair<string, boolean>("isAdmin", true);
console.log(pair2.getKey(), pair2.getValue()); // 输出: isAdmin true

示例:        // 泛型数组的类

class MyList<T> {
    private items: T[] = [];

    add(item: T): void {
        this.items.push(item);
    }

    remove(item: T): void {
        this.items = this.items.filter(i => i !== item);
    }

    getAll(): T[] {
        return this.items;
    }
}

// 创建一个存储数字的列表
const numberList = new MyList<number>();
numberList.add(10);
numberList.add(20);
numberList.remove(10);
console.log(numberList.getAll()); // 输出: [20]

// 创建一个存储字符串的列表
const stringList = new MyList<string>();
stringList.add("apple");
stringList.add("banana");
console.log(stringList.getAll()); // 输出: ["apple", "banana"]

示例:        // 泛型类指定默认数据类型

class Box<T = string> {  // 默认类型是 string
    private value: T;

    constructor(value: T) {
        this.value = value;
    }

    getValue(): T {
        return this.value;
    }
}

// 默认情况下 T 是 string
const defaultBox = new Box("Hello");
console.log(defaultBox.getValue()); // 输出: Hello

// 但仍然可以指定其他类型
const numberBox = new Box<number>(123);
console.log(numberBox.getValue()); // 输出: 123

约束泛型类型

在 TypeScript 中,约束泛型类型 允许我们限制泛型参数,使其必须符合某种结构或接口,而不能是任意类型。这样可以在保证灵活性的同时,提供额外的类型安全。

示例:        // 约束泛型必须是某个类

class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    speak(): void {
        console.log(`${this.name} makes a noise.`);
    }
}

// 约束 T 必须是 Animal 或其子类
class AnimalBox<T extends Animal> {
    private animal: T;

    constructor(animal: T) {
        this.animal = animal;
    }

    makeSound(): void {
        this.animal.speak();
    }
}

class Dog extends Animal {
    speak(): void {
        console.log(`${this.name} barks!`);
    }
}

const dogBox = new AnimalBox(new Dog("Buddy"));
dogBox.makeSound(); // 输出: Buddy barks!

// 传入非 Animal 类型的对象会报错
// const numBox = new AnimalBox(123); // Type error!

示例:        // 约束泛型类型必须实现某个接口

interface Printable {
    print(): void;
}

class Doc implements Printable {
    print(): void {
        console.log("Printing document...");
    }
}

class Photo implements Printable {
    print(): void {
        console.log("Printing photo...");
    }
}

// 约束 T 必须实现 Printable
class Printer<T extends Printable> {
    private item: T;

    constructor(item: T) {
        this.item = item;
    }

    printItem(): void {
        this.item.print();
    }
}

const docPrinter = new Printer(new Doc());
docPrinter.printItem(); // 输出: Printing document...

const photoPrinter = new Printer(new Photo());
photoPrinter.printItem(); // 输出: Printing photo...

// 传入不符合 Printable 接口的对象会报错
// const numPrinter = new Printer(123); // Type error!

示例:        // 约束泛型 T 同时满足多个约束,可以使用 & 运算符

interface Named {
    name: string;
}

interface Printable {
    print(): void;
}

// 约束 T 既要有 name,又要有 print()
class NamedPrinter<T extends Named & Printable> {
    private item: T;

    constructor(item: T) {
        this.item = item;
    }

    printNameAndItem(): void {
        console.log(`Name: ${this.item.name}`);
        this.item.print();
    }
}

class MyReport implements Named, Printable {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    print(): void {
        console.log(`Printing report: ${this.name}`);
    }
}

const reportPrinter = new NamedPrinter(new MyReport("Annual Report"));
reportPrinter.printNameAndItem();
// 输出:
// Name: Annual Report
// Printing report: Annual Report

// 传入没有 print 方法的对象会报错
// const invalidPrinter = new NamedPrinter({ name: "Test" }); // Type error!