ts保姆级学习指南

news2024/12/24 9:20:55

什么是 TypeScript?

TypeScript,简称 ts,是 JavaScript 的超集,而且它最大的特点之一就是引入了静态类型支持。这意味着开发者可以在 TypeScript 中定义变量、函数参数等的类型,编译器会在编译时进行类型检查,从而帮助开发者捕捉到一些在 JavaScript 中难以发现的错误,比如类型不匹配、未定义的属性等问题。这种类型检查能够提升代码的可靠性和可维护性,尤其是在大型项目中特别有用。

另外,TypeScript 还支持最新的 ECMAScript 标准,因此可以使用 JavaScript 的最新特性,同时还能将 TypeScript 编译成兼容各种 JavaScript 版本的代码,以确保跨平台的兼容性。

总之,TypeScript 不仅仅是 JavaScript 的一个扩展,它通过引入静态类型检查和其他语言特性,使得开发大型应用程序更加高效、可靠。

那么 ts 和 js 有什么区别呢?

TypeScript(简称为TS)和JavaScript(简称为JS)之间主要有几个区别:

类型系统:

  • TypeScript 引入了静态类型系统,允许开发者在声明变量、函数参数、返回值等时指定类型。编译器会进行类型检查,检测类型不匹配的错误。
  • JavaScript 是动态类型语言,变量类型在运行时确定,不需要显式声明类型。

编译:

  • TypeScript 需要先经过编译成 JavaScript 才能在浏览器或者其他 JavaScript 运行环境中执行。
  • JavaScript 是一种解释型语言,可以直接在浏览器或其他环境中执行。

生态系统:

  • JavaScript 生态系统非常广泛,有大量的库、框架和工具支持,适用于各种开发场景。
  • TypeScript 生态系统在不断扩展中,许多流行的 JavaScript 库和框架已经或者正在增加对 TypeScript 的支持。

错误检测:

  • TypeScript 在编译阶段能够检测出许多类型相关的错误,例如变量类型不匹配、未定义的属性访问等,帮助提前发现和修复潜在的问题。
  • JavaScript 的错误通常是运行时才能被发现的,因为它没有静态类型检查。

语言特性:

  • TypeScript 支持最新的 ECMAScript 标准,可以使用 JavaScript 新增的特性,并且还引入了一些自身的语言特性(如接口、枚举、泛型等),增强了代码的表达能力和可读性。
  • JavaScript 语法较为灵活,但在处理复杂的数据结构和大规模代码时,可能会因为缺乏类型检查而带来一些维护上的挑战。

如何安装与使用ts

1、安装node.js(可参考这篇文章)

2、下载一个全局的包

npm i  -g  typescript
或者
yarn  global add  typescript 

打开cmd 输入命令 tsc -v 查看包是否下载成功,当 cmd 输入指令后出现版本号,就说明下载成功啦

3、打开 vscode, 新建一个 .ts文件。如 hello.ts

4、在vscode商店安装Code Runner(具体用法)、Error Lens(自动显示代码错误提示)插件,方便后续学习

5、书写代码

console.log("Hello, world!");

 运行后成功在终端打印 Hello, world! 


TypeScript 极速学习

类型声明

类型声明(Type Annotations)是 TypeScript 中的一种语法,用于显式地指定变量、函数参数、函数返回值等的类型。通过类型声明,开发者可以告诉 TypeScript 编译器在编译时进行静态类型检查,从而捕获可能的类型错误。

在 TypeScript 中,类型声明通常使用 : 符号来指定,

变量声明

let num: number = 5;
let message: string = "Hello, TypeScript!";
let isActive: boolean = true;

函数参数和返回值声明

function add(x: number, y: number): number {
    return x + y;
}

上面的例子中,add 函数接受两个参数 x 和 y,它们都是 number 类型,并且函数的返回值也声明为 number 类型。

对象和数组声明

let person: { name: string, age: number } = {
    name: "Alice",
    age: 30
};

let numbers: number[] = [1, 2, 3, 4, 5];

person 对象中的 name 是 string 类型,age 是 number 类型。numbers 数组中的元素都是 number 类型。 

类型推断

TypeScript 还支持类型推断,即编译器可以根据变量的初始化值自动推断出其类型,例如:

let someNumber = 10; // TypeScript 自动推断 someNumber 是 number 类型

类型总览

注意点: JS 中的三个构造函数: Number 、 String 、 Boolean ,他们只⽤于包装对象,正
常开发时,很少去使⽤他们,在TS 中也是同理。

常⽤类型

字⾯量

在 TypeScript 中,字面量是指表示固定值的直接量,例如直接写入的数值、字符串或布尔值。在类型系统中,字面量可以用来精确地指定变量、函数参数或对象属性的取值范围,从而增强类型的精确度和可读性。

数字字面量

let num: 5 = 5;
let anotherNum: 10 | 20 | 30 = 20; // 表示变量可以取 10、20 或 30 中的一个值
let anotherNum2: 10 | 20 = 30; // 表示变量可以取 10 或 20 中的一个值,但 30 不能赋值给这个变量

字符串字面量:

let str: "hello" = "hello";
type Direction = "up" | "down" | "left" | "right"; // 定义一个字符串字面量联合类型

布尔字面量:

let isOpen: true = true;

对象字面量

let person: { name: "Alice", age: 30 } = { name: "Alice", age: 30 };

any

any 类型在 TypeScript 中的含义是允许变量可以是任意类型。将一个变量声明为 any 类型后,TypeScript 编译器会跳过对该变量的类型检查,这意味着:

  • 类型检查放宽:使用 any 类型后,TypeScript 不会对该变量的使用进行类型检查。这使得你可以对这个变量执行任何操作,即使这些操作在 TypeScript 中可能不安全或不符合预期的类型约束。
  • 运行时类型转换:any 类型的变量可以随意赋予任何类型的值,不会触发类型错误或警告。这种灵活性在一些特定情况下可能是必要的,比如处理来自动态数据源的数据。
  • 失去类型安全性:由于放弃了类型检查,使用 any 类型的变量可能导致运行时错误,因为 TypeScript 不会在编译阶段检测出潜在的类型不匹配问题。

虽然 any 类型在某些情况下提供了灵活性,但过度使用它会导致代码失去 TypeScript 提供的静态类型检查的主要好处。推荐在能够确定类型的情况下,尽量避免使用 any 类型,

//明确的表示a的类型是any —— 显式的any
let a: any
//以下对a的赋值,均⽆警告
a = 100
a = '你好'
a = false
//没有明确的表示b的类型是any,但TS主动推断了出来 —— 隐式的any
let b
//以下对b的赋值,均⽆警告
b = 100
b = '你好'
b = false
/* 注意点:any类型的变量,可以赋值给任意类型的变量 */
let a1
let x: string
x = a1 // ⽆警告

unknown

unknown 类型确实与 any 类型有相似之处,但在类型安全方面更加严格和精确。

  • 类型安全性:unknown 类型比 any 类型更加类型安全。当一个变量被声明为 unknown 类型时,你不能直接对它进行操作或者将它赋给其他类型的变量,除非进行类型检查或者类型断言。
  • 用途:unknown 类型适用于那些一开始无法确定具体类型,但后续可以进行类型检查并确定类型的情况。这种特性使得 unknown 更适合在运行时进行类型检查的场景,以确保类型安全性。
// 设置a的类型为unknown
let a: unknown
//以下对a的赋值,均正常
a = 100
a = false
a = '你好'
// 设置x的数据类型为string
let x: string
x = a //警告:不能将类型“unknown”分配给类型“string”

若就是想把a 赋值给 x ,可以⽤以下三种写法:

1、类型断言 (as 语法):

使用类型断言可以告诉编译器,你已经进行了类型检查,确定 a 是一个 string 类型。

// 设置a的类型为unknown
let a: unknown
a = 'hello'
let x: string;
x = a as string;  // 使用类型断言将 unknown 类型断言为 string 类型

2、typeof 类型保护:

使用 typeof 运算符进行类型保护,这种方式在你已知 a 的类型可以是 string 的情况下比较适用。

// 设置a的类型为unknown
let a: unknown
a = 'hello'
let x: string;
if (typeof a === 'string') {
    x = a;  // 在类型检查后直接赋值
}

3、自定义类型保护函数:

可以定义一个自定义的类型保护函数,以在多个地方重复使用。

// 设置a的类型为unknown
let a: unknown
a = 'hello'
function isString(value: unknown): value is string {
    // 判断 value 的类型是否为 'string'
    return typeof value === 'string';
}

let x: string;
if (isString(a)) {
    x = a;  // 使用自定义类型保护函数进行类型检查和赋值
}

never

never 类型是一个特殊的类型,表示那些永远不应该出现的值的类型。

1、几乎不用 never 直接限制变量:

never 类型通常不会直接用于限制变量,因为它是一个空集合的类型,意味着它不能包含任何值。因此,将一个变量显式声明为 never 意味着你永远不可能给这个变量赋值,这在大多数情况下是没有意义的。

/* 指定a的类型为never,那就意味着a以后不能存任何的数据了 */
let a: never
// 以下对a的所有赋值都会有警告
a = 1
a = true
a = undefined
a = null

2、never 一般是 TypeScript 主动推断出来的:

never 类型通常是 TypeScript 在类型推断中推断出来的结果,特别是在处理函数或者条件分支时。例如,一个函数如果抛出异常或者总是抛出错误,它的返回类型就会被推断为 never。

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

在这个例子中,throwError 函数的返回类型是 never,因为它永远不会正常返回,而是会抛出一个错误。 

3、never 用于限制函数的返回值:

never 类型可以用来明确指定函数永远不会返回任何值。这在一些特殊的情况下很有用,比如处理不可到达的终止点。

function infiniteLoop(): never {
    while (true) {
        // 无限循环,不返回任何值
    }
}

另外,never 类型还可以用于类型守卫中,帮助编译器理解一些条件下的类型信息,例如:

function isNever(value: any): value is never {
    throw new Error("This function should never be called.");
}

总结来说,never 类型在 TypeScript 中主要用于表示不可能存在的值或者永远不会发生的情况,例如函数抛出异常、无限循环等。它通常不直接用于限制普通变量,而是通过函数返回类型或类型推断来使用。

void

在 TypeScript 中,void 类型表示函数没有返回值,或者说函数返回 undefined。它表示函数执行完毕后没有任何返回值。

严格模式下不能将 null 赋值给 void 类型:

  • TypeScript 的严格模式下,不能将 null 赋值给 void 类型变量。因为 void 类型只能接受 undefined,而不是 null。这符合 void 类型的定义,即函数没有明确的返回值。

void 常用于限制函数返回值:

  • 在函数声明中,当函数没有明确的 return 语句或者返回值时,其返回类型会被推断为 void 类型。这在你希望函数执行完毕后不返回任何值时非常有用。
function logMessage(message: string): void {
    console.log(message);
    // 没有 return 语句,返回类型被推断为 void
}

总结来说,void 类型用于表示函数没有返回值,不能被赋予除了 undefined 之外的其他值。它是在 TypeScript 中用来强制限制函数的返回值的一种有用工具。

object

1、object 的含义:

object 小写形式在 TypeScript 中表示任何非原始值类型,包括对象、函数、数组等。它是 TypeScript 的一种基础类型之一,限制的范围较宽泛。

let obj: object;
obj = {};       // 合法,空对象
obj = []        // 合法,数组
obj = () => {}; // 合法,函数

由于 object 包含了许多复杂类型,它通常用得比较少,因为具体类型信息更有利于类型检查和代码理解。

2、Object 的含义:

Object 大写形式表示 JavaScript 中的实例对象,即所有非原始类型的对象。这包括普通对象、数组、函数等等。由于它包含了 JavaScript 中几乎所有的对象实例,因此在类型限制时使用 Object 会非常宽泛,通常来说几乎不用。

let obj: Object;
obj = {};       // 合法,空对象
obj = [];       // 合法,数组
obj = () => {}; // 合法,函数

由于 Object 的范围太大,它在类型限制时几乎没有实际应用的意义,因为无法有效地约束具体的对象类型。

3、实际开发中的使用建议:

限制一般对象:通常使用具体的对象字面量类型来限制,例如 {} 表示空对象,或者定义具有特定属性的接口类型。

// 限制person对象的具体内容,使⽤【,】分隔,问号代表可选属性
let person: { name: string, age?: number }
// 限制car对象的具体内容,使⽤【;】分隔,必须有price和color属性,其他属性不去限制,有没有都⾏
let car: { price: number; color: string;[k: string]: any }
// 限制student对象的具体内容,使⽤【回⻋】分隔
let student: {
    id: string
    grade: number
}
// 以下代码均⽆警告
person = { name: '张三', age: 18 }
person = { name: '李四' }
car = { price: 100, color: '红⾊' }
student = { id: 'tetqw76te01', grade: 3 }

4、限制函数的参数、返回值:

使用具体的函数签名来限制函数的参数类型和返回值类型。

function greet(name: string): void {
    console.log(`Hello, ${name}!`);
}

5、限制数组:

使用特定的数组类型来限制数组元素的类型。

let numArr: number[]; // 限制为数字类型的数组
let strArr: string[]; // 限制为字符串类型的数组

总结来说,object 用于广泛表示非原始类型的值,而 Object 范围过于宽泛且不常用。

tuple

Tuple(元组)确实是 TypeScript 中的一种特殊数组类型,它具有固定长度和固定类型顺序的特点。在 TypeScript 中,元组允许我们定义一个数组,其中每个位置的元素都有固定的类型。

例如,如果我们想要一个包含字符串和数字的元组,可以这样定义:

let tupleExample: [string, number];

tupleExample = ['hello', 10]; // 合法
tupleExample = [10, 'hello']; // 错误,顺序和类型不匹配
tupleExample = ['hello', 10, 'world']; // 错误,长度不匹配

在这个例子中,tupleExample 是一个包含两个元素的元组,第一个元素是字符串类型,第二个元素是数字类型。由于元组的长度和类型是固定的,因此在赋值时需要保证元素顺序和类型匹配,并且不能超出指定的长度。

元组在一些特定场景下很有用,例如表示固定长度和特定顺序的数据结构,例如坐标 [x, y] 或者日期 [year, month, day] 等。

enum

Enum(枚举)是 TypeScript 中的一种特殊数据类型,用于定义数值集合,使代码更具可读性和可维护性。枚举类型可以帮助开发者在代码中使用有意义的符号名称来表示数值,而不是直接使用数值或字符串。

// 定义枚举 Direction
enum Direction {
    Up = 1,     // 从1开始计数
    Down,       // 自动递增,Down = 2
    Left,       // Left = 3
    Right       // Right = 4
}

// 声明一个变量并赋予枚举值
let playerDirection: Direction = Direction.Down;

// 使用 switch 语句处理枚举值
switch (playerDirection) {
    case Direction.Up:
        console.log("向上移动");
        break;
    case Direction.Down:
        console.log("向下移动");
        break;
    case Direction.Left:
        console.log("向左移动");
        break;
    case Direction.Right:
        console.log("向右移动");
        break;
    default:
        break;
}

⾃定义类型

自定义类型可以让我们更灵活地限制变量的类型,以适应特定需求或约束。在 TypeScript 中,我们可以使用接口(interface)或类型别名(type alias)来自定义类型。

使用接口定义类型

// 定义一个接口来描述一个用户对象的结构
interface User {
    id: number;
    username: string;
    email: string;
    age?: number; // 可选属性
}

// 声明一个符合 User 接口的变量
let newUser: User = {
    id: 1,
    username: "john_doe",
    email: "john.doe@example.com",
    age: 30
};

在上面的例子中,User 接口定义了一个用户对象的结构,包括必须的 id、username 和 email 属性,以及一个可选的 age 属性。通过使用接口,我们可以确保 newUser 变量符合指定的结构,从而提高代码的可读性和可维护性。

使用类型别名定义类型

// 使用类型别名定义一个字符串或数组
type StringOrArray = string | string[];

// 使用类型别名声明一个变量
let data: StringOrArray;

// 此时 data 可以是字符串或字符串数组
data = "hello";
data = ["apple", "banana"];

在这个例子中,StringOrArray 是一个类型别名,可以表示字符串或字符串数组。这样的自定义类型别名使得我们可以在多种情况下灵活使用同一种类型约束。

抽象类

抽象类是 TypeScript 中的一种特殊类别,它不能直接实例化,而是作为其他类的基类来使用。抽象类主要用于定义其他类的结构和行为,但本身不会被实例化。

定义抽象类

要定义一个抽象类,需要在类声明前加上 abstract 关键字,并且可以包含抽象方法和非抽象方法。

// 定义一个抽象类 Animal
abstract class Animal {
    // 抽象方法,没有具体实现,子类必须实现它
    abstract makeSound(): void;

    // 非抽象方法,具有默认的实现
    move(): void {
        console.log("roaming the earth...");
    }
}

// 不能直接实例化抽象类
// let animal = new Animal(); // Error: 无法创建抽象类的实例

// 定义一个继承自抽象类的具体类 Dog
class Dog extends Animal {
    makeSound(): void {
        console.log("Woof! Woof!");
    }
}

// 创建 Dog 类的实例
let dog = new Dog();
dog.makeSound(); // 输出: Woof! Woof!
dog.move();      // 输出: roaming the earth...

注意

  • 不能被实例化: 抽象类本身不能被实例化,只能作为其他类的父类来使用。
  • 包含抽象方法: 抽象类中可以包含抽象方法,这些方法在子类中必须被实现。
  • 可以包含具体方法: 抽象类中也可以包含具体的方法实现,子类可以选择性地重写这些方法。
  • 用于约束和扩展: 抽象类常用于约束子类的行为和结构,提供了一种模板或蓝图的作用,使得代码更加模块化和可扩展。

接口

接口是用来描述类或对象应该具备的结构,包括属性和方法。类可以实现(implement)接口,从而强制类中包含接口中定义的所有属性和方法。

注意:在 TypeScript 中,接口可以在不同的地方重复声明,但是它们会被合并成单一的接口定义。这种合并的规则包括属性签名的合并以及方法重载的合并。

// 定义一个 Animal 接口
interface Animal {
    name: string; // 名称
    age: number; // 年龄
    speak(): void; // 定义一个抽象的 speak 方法,没有具体实现
}

// 实现接口的类
class Dog implements Animal {
    name: string; // 名称
    age: number; // 年龄

    constructor(name: string, age: number) {
        // 将传入的name参数赋值给类的name属性
        this.name = name;
        // 将传入的age参数赋值给类的age属性
        this.age = age;
    }

    speak(): void {
        // 输出带有名字和汪汪的消息
        console.log(`${this.name} 说 汪汪!${this.name}今年${this.age}岁了`);
    }
}

class Cat implements Animal {
    name: string; // 名称
    age: number; // 年龄

    constructor(name: string, age: number) {
        // 将传入的name赋值给this.name
        this.name = name;
        // 将传入的age赋值给this.age
        this.age = age;
    }

    speak(): void {
        // 输出字符串,格式为:this.name + " 说 喵喵!"
        console.log(`${this.name} 说 喵喵!${this.name}今年${this.age}岁了`);
    }
}

// 使用实现了接口的类
let dog = new Dog("小白", 3);
let cat = new Cat("小花", 2);

dog.speak(); // 输出: 小白 说 汪汪!小白今年3岁了
cat.speak(); // 输出: 小花 说 喵喵!小花今年2岁了

【接口】与【自定义类型】的区别:

接口和自定义类型(也称为类型别名)在功能上有所不同:

接口(Interfaces):

  • 可以描述对象的结构,包括属性、方法、索引签名等。
  • 可以被类实现(implement),从而强制类符合接口定义的结构。
  • 可以被用作类型声明,约束变量、函数或类的结构。

自定义类型(Type Aliases):

  • 仅仅是给一个类型起一个新名字,可以用来简化复杂类型的引用。
  • 不能描述方法或行为,仅用来定义已有类型的别名。

【接口】与【抽象类】的区别:

接口和抽象类都是用来定义规范,但它们有着不同的特性和用途:

抽象类(Abstract Classes):

  • 可以包含普通方法的实现和抽象方法的声明。
  • 可以被继承(extends)。
  • 可以包含构造函数。
  • 可以有访问修饰符(public、protected、private)。

接口(Interfaces):

  • 只能包含抽象方法的声明,没有方法的实现。
  • 类通过实现(implements)接口来强制符合接口定义的结构。
  • 不能包含字段(fields)、构造函数或任何实现代码。

总结来说,接口适合用于描述对象的结构和行为规范,而抽象类则更适合于具备某些默认行为的类的继承和实现。自定义类型则是简化已有类型的引用的一种方式,不具备描述结构或行为的能力。

属性修饰符

在 TypeScript 中,有几种属性修饰符可以用来控制类成员的可访问性和可见性。主要的属性修饰符包括:

  • public:默认修饰符,如果没有指定修饰符,成员被视为 public。public 成员在类内部、子类和类的外部均可访问。
  • private:私有成员只能在定义了这个成员的类的内部访问。即使是继承自该类的子类也不能访问其私有成员。
  • protected:受保护成员在定义了这个成员的类内部及其子类中可访问。但是在类的外部不能访问受保护成员。
  • readonly:只读成员必须在声明时或构造函数内被初始化,并且无法再被修改。可以将类的属性声明为 readonly,这样在实例化后就无法再修改它们的值了。
class Animal {
    public name: string;     // 默认为 public
    private age: number;     // 私有成员,只能在 Animal 类内部访问
    protected habitat: string;  // 受保护成员,可以在 Animal 及其子类内部访问
    readonly species: string;   // 只读成员,必须在声明时或构造函数内初始化

    /**
     * 构造函数
     *
     * @param name 名称
     * @param age 年龄
     * @param habitat 栖息地
     * @param species 物种
     */
    constructor(name: string, age: number, habitat: string, species: string) {
        this.name = name;
        this.age = age;
        this.habitat = habitat;
        this.species = species;
    }

    public displayInfo(): void {
        console.log(`${this.name} is a ${this.age} years old ${this.species} living in ${this.habitat}.`);
    }
}

class Dog extends Animal {
    constructor(name: string, age: number) {
        super(name, age, 'House', 'Dog');
        // this.age 是私有成员,子类无法直接访问
        // this.habitat 是受保护成员,可以在子类中访问
    }

    public makeSound(): void {
        // 可以访问从父类继承的 public 和 protected 成员
        console.log(`${this.name} says Woof!`);
        console.log(`${this.name} lives in ${this.habitat}.`);
        // console.log(`${this.name} is ${this.age} years old.`); // 错误:age 是私有成员,无法访问
    }
}

// 测试
let myDog = new Dog('Buddy', 3);
myDog.displayInfo(); // 可以访问父类的公共方法
// myDog.age = 4; // 错误:age 是私有成员,无法直接访问
// myDog.species = 'Golden Retriever'; // 错误:species 是只读成员,无法修改

泛型

定义⼀个函数或类时,有些情况下⽆法确定其中要使⽤的具体类型(返回值、参数、属性的类型不能确
定),此时就需要泛型了
举例: <T> 就是泛型,(不⼀定⾮叫 T ),设置泛型后即可在函数中使⽤ T 来表示该类型:

function test<T>(arg: T): T {
    return arg;
}
// 不指名类型,TS会⾃动推断出来
test(10)
// 指名具体的类型
test<number>(10)

在 TypeScript 中,泛型不仅限于单一类型参数,可以定义多个泛型参数来增加函数或类的灵活性。这种能力允许你在一个函数或类中使用多个不同类型的参数,并保持类型安全和通用性。

// 定义一个多个泛型参数的函数
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

// 使用方式一:明确指定类型
let pair1: [string, number] = pair<string, number>('hello', 5); // 返回 ['hello', 5]

// 使用方式二:类型推断
let pair2 = pair('world', true); // 返回 ['world', true],自动推断为 [string, boolean]

当然,泛型不仅限于函数,也可以在 TypeScript 类中使用。类中的泛型能够增强类的灵活性,允许你在定义类时不指定具体的数据类型,而是推迟到实例化类的时候再确定类型。

// 定义一个泛型类
class Pair<T, U> {
    private first: T;
    private second: U;

    constructor(first: T, second: U) {
        this.first = first;
        this.second = second;
    }

    getFirst(): T {
        return this.first;
    }

    getSecond(): U {
        return this.second;
    }
}

// 使用方式一:明确指定类型
let pair1: Pair<string, number> = new Pair<string, number>('hello', 5);
console.log(pair1.getFirst()); // 输出 'hello'
console.log(pair1.getSecond()); // 输出 5

// 使用方式二:类型推断
let pair2 = new Pair('world', true);
console.log(pair2.getFirst()); // 输出 'world',自动推断为 string
console.log(pair2.getSecond()); // 输出 true,自动推断为 boolean

当然也可以对泛型的范围进⾏约束。通过约束泛型,你可以限制泛型参数必须是某种特定类型,从而在编译时捕获一些错误,同时仍然保持灵活性。

// 定义一个接口,用于约束泛型类型必须具备的属性
interface Printable {
    print(): void;
}

// 泛型类,约束泛型参数必须实现 Printable 接口
class Printer<T extends Printable> {
    private item: T;

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

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

// 实现 Printable 接口的具体类
class Book implements Printable {
    constructor(private title: string) {}

    print() {
        console.log(`打印书籍:${this.title}`);
    }
}

class Car {
    constructor(private brand: string) {}

    // 这个类没有实现 Printable 接口
}

// 使用泛型类 Printer,并传入符合约束的类型
let bookPrinter = new Printer<Book>(new Book('TypeScript手册'));
bookPrinter.printItem(); // 输出 "打印书籍:TypeScript手册"

// 如果尝试传入不符合约束的类型将会报错
// let carPrinter = new Printer<Car>(new Car('Toyota')); // 这行代码会导致编译时错误

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1973643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu配置Ngbatis学习环境

引言 经过考虑&#xff0c;我感觉与NebulaGraph交互的ORM框架还是Ngbatis好。因为现在这个框架开发的比较完善&#xff0c;而且还在不断更新&#xff0c;社区活跃的用户多。从今日开始学习&#xff0c;首先要配置一下环境。 1.安装maven和jdk 选择的版本是maven3.8和jdk17.以…

iPhone可运行的谷歌Gemma 2 2B模型,性能超GPT-3.5

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…

关于inet_addr()中的参数不能是 sring类型的 只能是 string类型变量.c_str()

源码展示&#xff1a; extern in_addr_t inet_addr (const char *__cp) __THROW inet_addr中的参数是const char *类型的 定义一个string 类型的ip 使用这个inet_addr()接口 local.sin_addr.s_addr inet_addr(ip_.c_str()); local.sin_addr.s_addr inet_addr(&ip_);…

ELK对业务日志进行收集

ELK对业务日志进行收集 下载httpd 进到文件设置收集httpd的文件进行 设置 编辑内容 用于收集日志的内容 将日志的内容发送到实例当中 input {file{path > /etc/httpd/logs/access_logtype > "access"start_position > "beginning"}file{path &g…

基于SpringBoot+Vue的健身俱乐部网站(带1w+文档)

基于SpringBootVue的健身俱乐部网站(带1w文档) 基于SpringBootVue的健身俱乐部网站(带1w文档) 该系统采用java技术&#xff0c;结合ssm框架使页面更加完善&#xff0c;后台使用MySQL数据库进行数据存储。系统主要分为三大模块&#xff1a;即管理员模块和用户模块、教练模块。本…

openstack之nova-conductor工作原理及常见问题处理

openstack之nova-conductor工作原理及常见问题处理 这里写目录标题 openstack之nova-conductor工作原理及常见问题处理一、简介1. 概念2. 作用3. 体系结构 二、组件1. nova-api2. nova-scheduler3. nova-compute4. nova-conductor5. nova-api-metadata6. nova-placement-api7. …

Java AI伪原创视频创作视频提取文案改写去水印系统小程序源码

&#x1f525;AI赋能创作新纪元&#xff01;伪原创视频文案提取改写去水印全能系统大揭秘 &#x1f680; 开篇&#xff1a;创意无界&#xff0c;AI来助力 在这个视觉盛行的时代&#xff0c;视频创作成为了表达自我、传递信息的重要方式。但你是否曾为寻找灵感、撰写文案、处理…

SD-WAN的两种方案及其价值

SD-WAN&#xff08;软件定义广域网&#xff09;作为一种新兴的网络架构解决方案&#xff0c;给企业网络带来了极大的灵活性和可扩展性。它允许企业以更低的成本将广泛分布的分支机构连接到数据中心或其他分支机构&#xff0c;同时还能优化网络性能和用户体验。 SD-WAN的工作原理…

C语言之“文件操作”

文章目录 1. 什么是文件&#xff1f;&#xff08;1. 为什么使用文件&#xff1f;&#xff08;2.什么是文件2.1 程序文件2.2 数据文件2.3 文件名 2. 二进制文件和文本文件&#xff1f;3. 文件的打开和关闭3.1 流和标准流3.2 文件指针(用来管理流的)3.3 文件的打开与关闭 4. 文件…

vitis (eclipse) 的Indexer不能搜索、不能跳转到函数和变量定义和声明不能打开调用层次的解决方法

在使用vitis(2021.1) 过程中&#xff0c;有一个非常方便实用的功能&#xff0c;就是在函数或变量等源代码上通过右键菜单或快捷键F3、F4、CtrlAltH&#xff0c;也可以按住Ctrl键然后鼠标停留在函数名或变量名上&#xff0c;点击出现的链接&#xff0c;可以跳转到函数或变量的定…

linux磁盘可视化分析工具

在 Linux 系统中&#xff0c;了解磁盘使用情况对于系统维护和优化至关重要。文件和目录随着时间的推移会占据大量磁盘空间&#xff0c;了解哪些部分占用的空间最多可以帮助我们更好地管理和清理磁盘。Baobab&#xff0c;也称为 GNOME Disk Usage Analyzer&#xff0c;是一款非常…

数据化信息时代中开源 AI 智能名片拓客微信小程序的角色与价值

摘要&#xff1a;本文深入探讨了数据化信息的特性&#xff0c;包括其数字化基础、多媒体表现、可转化性及增值利用特点。同时&#xff0c;着重阐述了开源 AI 智能名片拓客微信小程序在这一背景下的重要作用和独特价值&#xff0c;为信息传播与利用提供了新的视角和思路。 关键…

WCF 禁止第三方访问,避免泄露元数据信息

开发的时候&#xff0c;服务端的web.config,将httpGetEnabled和httpsGetEnabled置true&#xff0c;这个时候客户端就可以添加服务引用。开发结束后&#xff0c;部署的时候&#xff0c;将这俩配置改成false

Bug 解决 | 后端项目无法正常启动,或依赖服务连接失败

目录 1、版本问题 2、依赖项问题 明明拷贝的代码&#xff0c;为什么别人行&#xff0c;我启动就报错&#xff1f; 这篇文章我就理一下最最常见的项目启动报错的两种原因&#xff01; 1、版本问题 比如明明项目的 Java 版本是 8&#xff0c;你非得拿 5 跑&#xff1f;那不是…

python爬虫预备知识三-序列化和反序列化

序列化和反序列化 序列化是为了将内存中的数据保存在磁盘上或者用于传输&#xff0c;实现程序状态的保存和共享。反序列化反之。 序列化后的变量再被反序列化回来之后&#xff0c;两者之间已经没有任何关系。 序列化后的文件是在不同程序或者说不同语言之间传递数据的关键方…

开发助手专业版,有反编译等多种功能

软件介绍 开发助手能够用来快速调试应用以及查看手机软硬件相关信息&#xff0c;包括&#xff1a;快速打开或关闭开发者选项中的选项。 将原来几十秒的操作缩短为一次点击。包括显示布局边界&#xff0c;显示 GPU 过度绘制。显示布局更新。强制 GPU 渲染 显示 GPU 视图更新&a…

第15课 Scratch少儿编程 入门篇:师生问候

师生问候 故事背景&#xff1a; 魔法学院的期末考核刚刚考完&#xff0c;魔法老师在教室里碰到小明&#xff0c;老师问小明考的怎么样&#xff1f; 程序原理&#xff1a; 找一个教室的背景&#xff0c;小精灵角色和魔法师的角色&#xff0c;将魔法师的角色造型左右反转&…

Java与Python谁更适合后端开发?

在软件开发的世界里&#xff0c;选择合适的编程语言就像为建筑选择合适的材料一样重要。 对于后端开发而言&#xff0c;Java和Python都是流行的选择&#xff0c;但它们各自拥有独特的优势和劣势&#xff0c;“谁更适合”就成为一个被议论的话题。 事实上&#xff0c;并不存在…

调用IP实现数据加速

前言 在数字系统设计中&#xff0c;提升数据处理速度和效率是关键目标。本实验着眼于利用双端口RAM和异步FIFO对IP核ROM中的数据进行加速处理&#xff0c;通过这两种硬件组件的有效结合来优化数据访问和传输。双端口RAM允许同时进行读写操作&#xff0c;提高数据处理的并行性和…

中央空调常用风口的分类

中央空调常用风口的分类中央空调常用的风口类型有七类&#xff0c;包括百叶风口、散流器、喷口、旋流风口、条缝风口、格栅风口和专用风口。详细分类如下&#xff1a; 1&#xff09;百叶风口&#xff1a;单层百叶风口、双层百叶风口、连动百叶风口、固定斜百叶风口、地面固定斜…