深入探索 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;
  // }
  // 类型守卫 2
  if ('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) {
    @nameWrapper
    this.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 应用程序中,可以使用装饰器来记录某个方法的调用时间,或者检查用户是否有执行某个方法的权限。通过将这些功能封装在装饰器中,可以使代码更加清晰和可维护,将业务逻辑与这些额外的功能分离开来。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】WaDec: Decompiling WebAssembly Using Large Language Model

论文阅读笔记:WaDec: Decompiling WebAssembly Using Large Language Model 1. 来源出处 论文标题: WaDec: Decompiling WebAssembly Using Large Language Model作者: Xinyu She, Yanjie Zhao, Haoyu Wang会议: 39th IEEE/ACM International Conference on Automated Softwar…

【安全测试】sqlmap工具(sql注入)学习

前言&#xff1a;sqimap是一个开源的渗透测试工具&#xff0c;它可以自动化检测和利用SQL注入缺陷以及接管数据库服务器的过程。它有一个强大的检测引擎&#xff0c;许多适合于终极渗透测试的小众特性和广泛的开关&#xff0c;从数据库指纹、从数据库获 取数据到访问底层文件系…

Redis环境部署(主从模式、哨兵模式、集群模式)

一、概述 REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库…

【Excel】身份证号最后一位“X”怎么计算

大多数人身份证号最后一位都是数字&#xff0c;但有个别号码最后一位却是“X"。 如果你查百度&#xff0c;会得到如下答案&#xff1a; 当最后一位编码是10的时候&#xff0c;因为多出一位&#xff0c;所以就用X替换。 可大多数人不知道的是&#xff0c;这个10是怎么来的…

Isaac Sim+SKRL机器人并行强化学习

目录 Isaac Sim介绍 OmniIssacGymEnvs安装 SKRL安装与测试 基于UR5的机械臂Reach强化学习测评 机器人控制 OMNI GYM环境编写 SKRL运行文件 训练结果与速度对比 结果分析 运行体验与建议 Isaac Sim介绍 Isaac Sim是英伟达出的一款机器人仿真平台&#xff0c;适用于做机…

Leetcode 743 Network Delay Time

题意&#xff1a;给定n个节点的网络&#xff0c;以及节点之间传输的时间&#xff0c;求从节点k出发传输信息&#xff0c;最少需要多久&#xff0c;所有的节点都能够接收到信息 https://leetcode.com/problems/network-delay-time/description/ 题解&#xff1a;给定一个有向图…

[Android]相关属性功能的裁剪

1.将home界面的search bar 移除 /src/com/android/launcher3/graphics/LauncherPreviewRenderer.java // Add first page QSBif (FeatureFlags.QSB_ON_FIRST_SCREEN) {CellLayout firstScreen mWorkspaceScreens.get(FIRST_SCREEN_ID);View qsb mHomeElementInflater.infla…

qt中ctrl+鼠标左键无法进入

现象&#xff1a;qt中ctrl鼠标左键无法跳转部分函数&#xff0c;例如能跳到textEdit->toPlainText().&#xff0c;但无法跳转到toUtf8();但编译没有问题 排查1&#xff1a;我发现是交叉编译链的问题&#xff0c;使用linux自带就可以进&#xff0c;用ATK-I.MX6U就部分不能进…

【Android】View—基础知识,滑动,弹性滑动

基础知识 什么是View 在 Android 中&#xff0c;View 是用户界面&#xff08;UI&#xff09;中的基本组件&#xff0c;用于绘制图形和处理用户交互。所有的 UI 组件&#xff08;如按钮、文本框、图片等&#xff09;都是 View 的子类。可以说&#xff0c;View 是构建 Android …

2024年十大信创操作系统之中科红旗的红旗 Linux

随着全球信息技术格局的变化与国家信息安全日益重要&#xff0c;操作系统作为计算机硬件与软件之间的中介&#xff0c;逐渐成为了国家竞争力的核心领域之一。尤其是在我国提出自主创新、国产替代的战略背景下&#xff0c;信创&#xff08;信息技术应用创新&#xff09;产业的快…

QT开发笔记之小知识

QCoreApplication::aboutToQuit 主事件循环退出前发出的信号&#xff0c;是程序退出前等待QT线程退出回收资源的神器。 官方帮助文档 [signal] void QCoreApplication::aboutToQuit() 该信号在应用程序即将退出主事件循环时发出&#xff0c;例如&#xff1a;当事件循环级别降至…

Word VBA如何间隔选中多个(非连续)段落

实例需求&#xff1a;Word文档中的有多个段落&#xff0c;段落总数量不确定&#xff0c;现在需要先选中所有基数段落&#xff0c;即&#xff1a;段落1&#xff0c;段落3 … &#xff0c;然后一次性设置粗体格式。 也许有的读者会认为这个无厘头的需求&#xff0c;循环遍历遍历文…

PyAEDT:Ansys Electronics Desktop API 简介

在本文中&#xff0c;我将向您介绍 PyAEDT&#xff0c;这是一个 Python 库&#xff0c;旨在增强您对 Ansys Electronics Desktop 或 AEDT 的体验。PyAEDT 通过直接与 AEDT API 交互来简化脚本编写&#xff0c;从而允许在 Ansys 的电磁、热和机械求解器套件之间无缝集成。通过利…

软件著作权申请教程(超详细)(2024新版)软著申请

目录 一、注册账号与实名登记 二、材料准备 三、申请步骤 1.办理身份 2.软件申请信息 3.软件开发信息 4.软件功能与特点 5.填报完成 一、注册账号与实名登记 首先我们需要在官网里面注册一个账号&#xff0c;并且完成实名认证&#xff0c;一般是注册【个人】的身份。中…

HTTPS详解:加密机制、工作流程、CA证书与中间人攻击防护

文章目录 1. 前言1.1. 什么是HTTPS1.2. 什么是加密1.3. 常见的加密方式① 对称加密② 非对称加密 1.4. 数据摘要&#xff08;数据指纹&#xff09;① 实例&#xff1a;软件分发中的数据摘要 1.5.1 一个小问题 2. HTTPS 工作流程探究2.1. 方案1 - 只使用对称加密2.2. 方案2 - 只…

机器学习基础04

目录 1.朴素贝叶斯-分类 1.1贝叶斯分类理论 1.2条件概率 1.3全概率公式 1.4贝叶斯推断 1.5朴素贝叶斯推断 1.6拉普拉斯平滑系数 1.7API 2.决策树-分类 2.1决策树 2.2基于信息增益的决策树建立 2.2.1信息熵 2.2.2信息增益 2.2.3信息增益决策树建立步骤 2.3基于基…

【Python · PyTorch】卷积神经网络(基础概念)

【Python PyTorch】卷积神经网络 CNN&#xff08;基础概念&#xff09; 0. 生物学相似性1. 概念1.1 定义1.2 优势1.2.1 权重共享1.2.2 局部连接1.2.3 层次结构 1.3 结构1.4 数据预处理1.4.1 标签编码① One-Hot编码 / 独热编码② Word Embedding / 词嵌入 1.4.2 归一化① Min-…

ospf排错学习

排错步骤是 1、查看ospf的router-id是否相同 2、错误配置ospf发布路由 //典型错误 3、错误的ospf区域号 4、错误的被动接口设置 //接口设置为被动接口&#xff0c;不学习了 排错思路&#xff08;思科命令&#xff09…

AR眼镜方案_AR智能眼镜阵列/衍射光波导显示方案

在当今AR智能眼镜的发展中&#xff0c;显示和光学组件成为了技术攻坚的主要领域。由于这些组件的高制造难度和成本&#xff0c;其光学显示模块在整个设备的成本中约占40%。 采用光波导技术的AR眼镜显示方案&#xff0c;核心结构通常由光机、波导和耦合器组成。光机内的微型显示…

【Linux】多线程(中)

目录 一、线程互斥 1.1 互斥概念 1.2 互斥量mutex 1.3 互斥量相关API &#xff08;1&#xff09;初始化互斥量 &#xff08;2&#xff09;销毁互斥量 &#xff08;3&#xff09;互斥量加锁和解锁 1.4 互斥量原理 1.5 重入和线程安全 二、死锁 2.1 概念 2.2 造成死锁…