当前位置: 首页 > news >正文

深入探索 TypeScript:从基础到高级特性

深入探索 TypeScript:从基础到高级特性

一、引言

在现代软件开发领域,TypeScript 已经成为了一种极具影响力的编程语言。它基于 JavaScript,并为其添加了强大的静态类型系统,使得代码在开发阶段就能进行更严格的类型检查,从而减少运行时错误,提高代码的质量和可维护性。无论是大型企业级应用还是小型的前端项目,TypeScript 都展现出了独特的优势。在这篇博客中,我们将深入探讨 TypeScript 的各个方面,包括它的安装、类型系统、接口、交叉类型、断言、泛型、装饰器和重载等特性,让你全面了解这一强大的编程工具。

二、TypeScript 的安装与初体验

(一)安装 TypeScript

要开始使用 TypeScript,首先需要在本地环境中安装它。使用 Node Package Manager(npm)可以轻松完成安装。在命令行中执行以下命令:

npm install -g typescript

这条命令会在全局环境中安装 TypeScript。安装完成后,可以通过以下命令检查 TypeScript 的版本:

tsc -v

这一步确保 TypeScript 已经正确安装在你的系统中,为后续的开发工作做好准备。

三、TypeScript 的类型系统

(一)基本类型

TypeScript 提供了丰富的基本类型,包括booleanstringnumberarraynullundefinedobject。这些基本类型构成了 TypeScript 类型系统的基础。

let isDone: boolean = false;
let str: string = 'hello';
let num: number = 123;
let u: undefined = undefined;
let n: null = null;
let arr: number[] = [1, 2, 3];
let strArr: string[] = ['a', 'b'];
let list: any[] = [1, true, 'hello'];
let x: [string, number] = ['hello', 10];
let StrArr: Array<string> = ['a', 'b'];

在上述代码中,我们可以看到不同基本类型的变量声明方式。boolean类型用于表示真假值,string类型用于存储文本数据,number类型用于表示数字。对于数组类型,可以使用类型[]或者Array<类型>的方式来声明。而nullundefined在 TypeScript 中有明确的类型定义,它们与其他类型的使用方式有所不同。

(二)元组(Tuple)

元组是一种特殊的数据类型,它允许我们表示一个已知元素数量和类型的数组,每个元素的类型可以不同。

let tupleType: [string, number] = ['hello', 10];

在这个例子中,tupleType是一个包含一个字符串和一个数字的元组。元组在处理一些具有特定结构的数据时非常有用,例如函数返回多个不同类型的值时,可以使用元组来接收。

(三)枚举(Enum)

枚举是一种将一组相关的命名常量组织在一起的方式,它为代码增加了可读性和可维护性。

  1. 数字枚举
    数字枚举默认从 0 开始依次递增。
enum Color {Red, Green, Blue};
let color: Color = Color.Green;

在这个例子中,Color枚举定义了三个颜色常量,Red的值为 0,Green的值为 1,Blue的值为 2。

  1. 字符串枚举
    除了数字枚举,还可以定义字符串枚举。
enum Color {Red = 'red', Green = 'green', Blue = 'blue'};
let color: Color = Color.Green;

字符串枚举使得每个枚举成员的值都明确指定为一个字符串,这种方式在代码即文档方面有很大优势,代码的可读性更强。

(四)特殊类型:any、unknown、void 和 never

  1. any 类型
    any类型是一种可以代表任意类型的值。它允许我们绕过类型检查,但过度使用any可能会导致类型系统的优势丧失。
let anyValue: any; // 任意类型 绕过 类型检查
anyValue = 'hello';
anyValue = 123;
anyValue = true;
anyValue = {};
  1. unknown 类型
    unknown类型表示一个未知类型的值。与any不同,unknown类型更加严格,在使用unknown类型的值时,需要先进行类型检查或断言。
let unknownValue: unknown;
unknownValue = 'hello';
// 以下代码会报错,因为 unknown 类型的值不能直接调用方法
// unknownValue.trim();
unknownValue = 123;
unknownValue = true;
unknownValue = {};
  1. any 和 unknown 的区别
    虽然anyunknown都可以绕过类型检查,但它们的严格程度不同。any可以赋值给任意类型,而unknown只能赋值给自身和any类型。

  2. void 类型
    void类型通常用于声明函数的返回值类型为空。

function warnUser(): void {console.log('This is a warning message');
}
  1. never 类型
    never类型用于声明函数永远不会返回值,例如抛出异常的函数。
function error(message: string): never {throw new Error(message);
}

voidnever的区别在于,void表示函数没有返回值(可以理解为返回undefined),而never表示函数根本不会正常返回。

(五)Object 类型

Object类型在 TypeScript 中有特定的含义,它代表标准对象,即非原始类型。

// 标准对象 => 非原始类型
interface MyObject {create(i: object | null): any
}
// Object.prototype

这里的object类型用于描述一个非原始类型的值,它可以是任何对象,包括通过new关键字创建的实例对象、对象字面量等。

四、接口(Interface)

(一)接口的基本概念

接口是 TypeScript 中一种重要的类型定义方式,它用于对行为的抽象,具体的行为则交给类来实现。接口定义了一组属性和方法的签名,类必须实现这些接口中定义的内容。

interface Class {name: string;time: number;
}
let classObj: Class = {name: 'TS',time: 2024
};

在这个例子中,Class接口定义了nametime两个属性,classObj对象实现了这个接口,它必须包含nametime属性,并且类型要与接口中定义的一致。

(二)接口的特性

  1. 只读属性(readonly)和可选属性(?)
    接口可以定义只读属性和可选属性。只读属性在初始化后不能被修改,可选属性则表示该属性在实现接口时可以存在也可以不存在。
interface Point {readonly x: number; // readonly 只读属性readonly y: number;z?: number; // 可选属性
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
  1. 面试:readonly vs const
    constreadonly都可以用于声明常量,但它们有一些区别。const声明常量后,必须赋值,且不能修改。
const num = 10;
num = 20; // error!
const obj = { a: 1, b: 2 };
obj.a = 3; // 这里对于对象内部属性的修改是允许的,因为 const 只是保证对象的引用不变

对于数组,我们可以通过ReadonlyArray类型来创建只读数组。

let arr: number[] = [1, 2];
let roArr: ReadonlyArray<number> = arr;
roArr[0] = 1; // error!
roArr.push(3); // error!
roArr.length = 1; // error!
arr = roArr; // error!
arr = [1, 2, 3]; // ok
roArr = arr; // ok
  1. 可添加性
    接口还可以定义任意属性,使用[propName: string]: any;的方式。
interface Point {x: number;y?: number;[propName: string]: any; // 任意属性
}
let p1: Point = { x: 10, z: 20, name: '坐标点位', color: 'red', err: '错误信息' };

这种方式使得接口在处理具有动态属性的对象时更加灵活,但需要注意避免滥用,以免影响类型的确定性。

五、交叉类型(&)

(一)交叉类型的概念

交叉类型允许我们将多个类型合并成一个类型,新的类型将包含所有参与交叉的类型的属性。

interface A {a: number;
}interface B {b: string;
}type AB = A & B; // 交叉类型
let ab: AB = { a: 1, b: 'hello' };

在这个例子中,AB类型是AB类型的交叉,ab对象必须同时满足AB接口的定义。

(二)更复杂的交叉类型示例

交叉类型可以用于更复杂的场景,例如多个接口之间的交叉。

interface A { x: D }
interface B { x: E }
interface C { x: F }interface D { d: number }
interface E { d: string }
interface F { d: boolean }type ABC = A & B & C;
let abc: ABC = { x: { d: true, e: 'hello', f: 10 } };

这里ABC类型是ABC三个接口的交叉,abc对象需要满足所有相关接口对于x属性的定义,虽然这个例子比较复杂,但展示了交叉类型在处理复杂类型关系时的强大能力。

(三)type(类型别名)vs interface(类型描述)

  1. 相同点
    两者都可以声明类型,并且都具有一定的可拓展性。

  2. 不同点

    • 声明次数interface可以声明多次,同名的interface会自动合并。例如:
interface Person {name: string;
}
interface Person {age: number;sex: string;
}

type不能声明两次,如果重复声明会报错。

- **修改限制**:`type`声明后,不能再次修改,它更像是一个一次性定义的类型别名。而`interface`在一定程度上可以通过多次声明来扩展。- **使用场景**:`type`更偏向于多重类型的动态计算,例如联合类型、交叉类型等复杂的类型操作。`interface`更偏向于描述对象的静态计算,因为它通常用于向外暴露类型定义,并且可以方便地进行扩展。

(四)面试 2:联合冲突

当使用交叉类型时,可能会遇到联合冲突的情况。

interface A {a: number;b: number;
}interface B {b: string;c: boolean;
}type AB = A & B;
let ab: AB = { a: 1, c: true };
// b: never 
// 因为没有一种类型可以同时满足 A 和 B,所以 b 为 never

在这种情况下,由于ABb属性的类型定义不一致,且没有共同的类型满足这两个接口,所以b的类型被推断为never

六、类型断言

(一)类型断言的概念

类型断言是一种告诉 TypeScript 编译器我们比它更了解某个值的类型的方式,它允许我们将一个值视为特定的类型,从而实现类型变量的多样化。

  1. as 语法
    使用as关键字进行类型断言。
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
  1. 尖括号语法(在某些情况下可能受限)
    在某些环境中,也可以使用尖括号的方式进行类型断言,但在 React 等一些 JavaScript 框架中,尖括号可能会与 JSX 语法冲突,所以as语法更为常用。
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
  1. 非空断言
    在处理可能为undefinednull的值时,可以使用非空断言。
type ClassTime = () => string | number;
const start = (classTime: ClassTime | undefined) => {let number = classTime!(); // 非空断言
};

(二)类型守卫

类型守卫是一种在代码中进行类型检查的机制,它可以保证在语法规定的多种类型范围内做校验。

interface Teacher {name: string;age: number;courses: string[];
}
interface Person {name: string;age?: number;
}function getInfo(person: Person | Teacher): string {// 类型守卫 1// if ((person as Teacher).courses!== undefined) {//   return (person as Teacher).name + '教' + (person as Teacher).courses.join('');// } else {//   return (person as Person).name;// }// 类型守卫 2if ('courses' in person) {return person.name + '教' + (person as Teacher).courses.join('');} else {return (person as Person).name;}
}

在这个例子中,我们通过in关键字来检查person对象是否具有courses属性,从而判断personTeacher类型还是Person类型。这种类型守卫的方式使得我们可以在处理联合类型的值时,根据不同的类型执行相应的逻辑。

七、泛型

(一)泛型的概念

泛型是 TypeScript 中一个非常强大的特性,它主要解决类、接口、方法的复用性问题,以及对不特定数据类型的支持。泛型允许我们编写可以在多种类型上工作的代码,而不是针对特定类型编写重复的代码。

function getData<T, U>(value: T, score: U): T {return value;// 类型推断
}
let data = getData<string, number>('hello', 123);

getData函数中,TU是泛型类型参数。当我们调用getData函数时,可以指定TU的具体类型,这样函数就可以根据传入的类型进行相应的类型检查和操作。同时,TypeScript 也具有类型推断的能力,在一些情况下可以自动推断出泛型的类型。

(二)泛型函数的更多示例

泛型函数可以有更复杂的形式和应用场景。

function getData<T, U>(value: T, score: U): String {return value;
}function getData<T, U>(value: T, score: U): String {return (String(value)) as any as T;
}

这些示例展示了泛型函数在处理不同类型参数和返回值类型时的灵活性,但需要注意在使用类型转换时要确保类型的安全性,避免出现意外的类型错误。

八、装饰器(Decorator)

(一)装饰器的概念

装饰器是一种特殊的函数,它可以用来修改类、方法或属性。装饰器提供了一种简洁的方式来为代码添加额外的功能或行为。

function log(target: Function): void {// 对 target 进行加工console.log(target);target.prototype.sayHello = function () {console.log('hello');}
}function nameWrapper(target: any, key: string): void {Object.defineProperty(target, key, {// setter// getter})
}
@log
class Person {name: string;age: number;constructor(name: string, age: number) {@nameWrapperthis.name = name;this.age = age;console.log('Person');console.log(this);console.log(this.name, this.age);console.log(this instanceof Person);}
}

在这个例子中,log装饰器函数接受一个类的构造函数作为参数,并为该类添加了一个sayHello方法。nameWrapper装饰器函数则可以用于修改类的属性的描述符,例如添加settergetter方法。通过使用装饰器,我们可以在不修改类的原始代码的情况下,为类添加新的功能。

(二)装饰器的应用场景

装饰器在许多场景中都有广泛的应用,比如日志记录、性能监测、权限验证等。例如,在一个 Web 应用程序中,可以使用装饰器来记录某个方法的调用时间,或者检查用户是否有执行某个方法的权限。通过将这些功能封装在装饰器中,可以使代码更加清晰和可维护,将业务逻辑与这些额外的功能分离开来。

相关文章:

深入探索 TypeScript:从基础到高级特性

深入探索 TypeScript&#xff1a;从基础到高级特性 一、引言 在现代软件开发领域&#xff0c;TypeScript 已经成为了一种极具影响力的编程语言。它基于 JavaScript&#xff0c;并为其添加了强大的静态类型系统&#xff0c;使得代码在开发阶段就能进行更严格的类型检查&#x…...

Leetcode:118. 杨辉三角——Java数学法求解

题目——Leetcode:118. 杨辉三角 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRow…...

SHELL脚本(Linux)

声明 学习视频来自 B 站UP主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 ✍&#x1f3fb;作者简介&#xff1a;致…...

单元测试、集成测试、系统测试、验收测试、压力测试、性能测试、安全性测试、兼容性测试、回归测试(超详细的分类介绍及教学)

目录 1.单元测试 实现单元测试的方法&#xff1a; 注意事项&#xff1a; 2.集成测试 需注意事项&#xff1a; 实现集成测试的方法&#xff1a; 如何实现高效且可靠的集成测试&#xff1a; 3.系统测试 实现系统测试的方法: 须知注意事项&#xff1a; 4.验收测试 实现验…...

低代码集成多方API的简单实现

在现代软件开发中&#xff0c;集成多个API服务提供商已成为常见需求。然而&#xff0c;不同的API认证机制和数据格式使得集成过程变得复杂且耗时。为了应对这些挑战&#xff0c;本文将介绍一种低代码解决方案&#xff0c;通过配置化管理和简化的代码逻辑&#xff0c;帮助开发者…...

【测试框架篇】单元测试框架pytest(1):环境安装和配置

一、pytest简介 Pytest是Python的一种单元测试框架&#xff0c;与Python自带的unittest测试框架类似&#xff0c;但是比 unittest框架使用起来更简洁&#xff0c;效率更高。 二、pytest特点 Pytest是一个非常成熟的Python测试框架,主要特点有以下几点&#xff1a; 非常容易…...

Python数据分析NumPy和pandas(二十九、其他Python可视化工具)

与其他开源工具一样&#xff0c;在 Python 中创建图形有很多选项&#xff08;太多了&#xff0c;无法一一列举&#xff09;。自 2010 年以来&#xff0c;主要开发工作集中在创建用于在 Web 上发布交互式图形上。例如&#xff1a; Altair、Bokeh 和 Plotly 等工具&#xff0c;可…...

Unity中HDRP设置抗锯齿

一、以前抗锯齿的设置方式 【Edit】——>【Project Settings】——>【Quality】——>【Anti-aliasing】 二、HDRP项目中抗锯齿的设置方式 在Hierarchy中——>找到Camera对象——>在Inspector面板上——>【Camera组件】——>【Rendering】——>【Pos…...

Spring Boot实现文件上传与OSS集成:从基础到应用

目录 前言1. 文件上传的基础实现1.1 前端文件上传请求1.2 后端文件接收与保存 2. 集成第三方OSS服务2.1 准备工作2.2 编写OSS集成代码2.3 修改Controller实现文件上传至OSS 3. 文件上传的扩展&#xff1a;多文件上传与权限控制结语 前言 随着互联网应用的快速发展&#xff0c;…...

Python学习26天

集合 # 定义集合 num {1, 2, 3, 4, 5} print(f"num&#xff1a;{num}\nnum数据类型为&#xff1a;{type(num)}") # 求集合中元素个数 print(f"num中元素个数为&#xff1a;{len(num)}") # 增加集合中的元素 num.add(6) print(num) # {1,2,3,4,5,6} # 删除…...

linux startup.sh shutdown.sh (kkFileView)

linux启动脚本和关闭脚本startup.sh shutdown.sh &#xff08;kkFileView&#xff09; startup.sh DIR_HOME("/opt/openoffice.org3" "/opt/libreoffice" "/opt/libreoffice6.1" "/opt/libreoffice7.0" "/opt/libreoffice7.1&q…...

[MySQL]隐式类型转换

安全等号 <> 如果有参数为NULL&#xff0c;则除了相等比较运算符()&#xff0c;比较的结果为null。对于 nullnull&#xff0c;结果为true。 在select语句中&#xff0c;使用 时&#xff0c;结果不会包含值为 null 的记录&#xff0c;但如果使用安全等号 <> 来…...

面经总结1

文章目录 如何保证批量请求失败&#xff0c;只弹出一个toast1使用计数器&#xff1a;2使用标志变量&#xff1a; 如何减少项目里的if-else1使用多态2使用策略模式3使用字典映射4使用状态模式 babel-runtime 作用是啥如何实现 PDF 预览和下载1浏览器内置PDF阅读器2使用PDF.js库3…...

Oracle19C AWR报告分析之Instance Efficiency Percentages (Target 100%)

Oracle19C AWR报告分析之Instance Efficiency Percentages 一、分析数据二、详细分析2.1 Instance Efficiency Percentages (Target 100%)各项指标及其解释2.2 分析和总结 一、分析数据 二、详细分析 在 Oracle AWR (Automatic Workload Repository) 报告中&#xff0c;每个性能…...

数据结构--数组

一.线性和非线性 线性&#xff1a;除首尾外只有一个唯一的前驱和后继。eg&#xff1a;数组&#xff0c;链表等。 非线性&#xff1a;不是线性的就是非线性。 二.数组是什么&#xff1f; 数组是一个固定长度的存储相同数据类型的数据结构&#xff0c;数组中的元素被存储在一…...

nrm的安装及使用

nrm的安装及使用 NRM&#xff08;NPM Registry Manager&#xff09;是一个用于快速切换npm&#xff08;Node Package Manager&#xff09;源的工具。npm是Node.js的包管理工具&#xff0c;用于安装、发布、管理Node.js包。由于网络原因&#xff0c;直接使用npm官方源&#xff…...

【MatLab手记】 --从0到了解超超超详过程!!!

文章目录 MatLab笔记一、命令行窗口二、变量命名规则三、数据类型1. 数字2. 字符与字符串3. 矩阵3.1 矩阵创建3.2 矩阵的修改和删除3.3 矩阵的拼接与重构重排3.4 矩阵的运算方法3.5 矩阵的下标 4. 元胞数组&#xff08;类似数据容器&#xff09;5. 结构体 四、逻辑与流程控制五…...

从零创建vue+elementui+sass+three.js项目

初始化&#xff1a; vue init webpack projectnamecd projectnamenpm install支持sass: npm install sass --save-dev npm install sass-loader7.1.0 --save-dev npm install node-sass4.12.0 --save-devbuild/webpack.base.conf.js添加 rules: [...,{test: /\.scss$/,loade…...

Linux通过使用scp和sftp发送或拉取文件

在通过 telnet 登录到远程服务器之后&#xff0c;你无法直接使用 telnet 发送文件。telnet 是一个纯文本协议&#xff0c;不支持文件传输。要发送文件&#xff0c;你需要使用其他工具&#xff0c;如 scp 或 sftp。以下是使用这两种工具发送文件的方法&#xff1a; 使用 scp 发…...

Jtti:服务器总是自动重启怎么办?

服务器总是自动重启可能是由于多种原因引起的&#xff0c;包括硬件故障、软件问题、配置错误或环境因素。以下是一些常见原因和相应的解决方案&#xff1a; 1. 硬件问题 电源故障&#xff1a;电源供应不稳定或电源模块故障可能导致服务器重启。 解决方案&#xff1a;检查电源供…...

北京大学c++程序设计听课笔记101

基本概念 程序运行期间&#xff0c;每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址&#xff08;也称“入口地址”&#xff09;。我们可以将函数的入口地址赋给一个指针变量&#xff0c;使该指针变量指向该函数。然后通过指针变量就可以调用这个…...

一键生成本地SSL证书:打造HTTPS安全环境

一键生成本地SSL证书&#xff1a;打造HTTPS安全环境 日光下的寒林没有一丝杂质&#xff0c;空气里的冰冷仿佛来自故乡遥远的北国&#xff0c;带着一些相思&#xff0c;还有细微几至不可辨认的骆驼的铃声。–《心美&#xff0c;一切皆美》 在本地开发环境中启用 HTTPS 一直是许多…...

Unity类银河战士恶魔城学习总结(P124 CharacterStats UI玩家的UI)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了玩家属性栏&#xff0c;仓库&#xff0c;物品栏UI的制作 UI_StatSlot.cs 这个脚本是用来在Unity的UI上显示玩家属性&#xf…...

速盾:cdn 支持 php 吗?

在网络开发中&#xff0c;PHP 是一种广泛使用的服务器端脚本语言&#xff0c;用于创建动态网页和 web 应用程序。CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;在内容分发方面具有强大的功能&#xff0c;那么它是否支持 PHP 呢&#xff1f; C…...

在linux中使用nload实时查看网卡流量

在Linux系统中&#xff0c;可以使用多种工具来查看网卡流量。以下是一些常用的命令行工具&#xff1a; ifconfig&#xff1a;这是最基本的网络接口查看命令&#xff0c;但在最新的Linux发行版中&#xff0c;ifconfig命令已经被ip命令替代。 ip&#xff1a;用来查看和操作路由…...

【JavaEE进阶】Spring 事务和事务传播机制

目录 1.事务回顾 1.1 什么是事务 1.2 为什么需要事务 1.3 事务的操作 2. Spring 中事务的实现 2.1 Spring 编程式事务(了解) 2.2 Spring声明式事务 Transactional 对比事务提交和回滚的日志 3. Transactional详解 3.1 rollbackFor 3.2 Transactional 注解什么时候会…...

Flink1.19编译并Standalone模式本地运行

1.首先下载源码 2.本地运行 新建local_conf和local_lib文件夹&#xff0c;并且将编译后的文件放入对应的目录 2.1 启动前参数配置 2.1.2 StandaloneSessionClusterEntrypoint启动参数修改 2.1.3 TaskManagerRunner启动参数修改 和StandaloneSessionClusterEntrypoint一样修改…...

gitlab-development-kit部署gitlab《二》

gitlab-development-kit部署gitlab《一》 环境 mac 12.7.4 xcode 14.2 gdk 0.2.16 gitlab-foss 13.7 QA xcode源码安装 # https://crifan.github.io/xcode_dev_summary/website/xcode_dev/install_xcode/ # https://xcodereleases.comopenssl1.1 源码安装 # https://open…...

Java面试之多线程并发篇(3)

前言 本来想着给自己放松一下&#xff0c;刷刷博客&#xff0c;突然被几道面试题难倒&#xff01;SynchronizedMap和ConcurrentHashMap有什么区别&#xff1f;什么是线程安全&#xff1f;Thread类中的yield方法有什么作用&#xff1f;Java线程池中submit() 和 execute()方法有…...

任何使用 Keras 进行迁移学习

在前面的文章中&#xff0c;我们介绍了如何使用 Keras 构建和训练全连接神经网络&#xff08;MLP&#xff09;、卷积神经网络&#xff08;CNN&#xff09;和循环神经网络&#xff08;RNN&#xff09;。本文将带你深入学习如何使用 迁移学习&#xff08;Transfer Learning&#…...