深入探索 TypeScript:从基础到高级特性
深入探索 TypeScript:从基础到高级特性
一、引言
在现代软件开发领域,TypeScript 已经成为了一种极具影响力的编程语言。它基于 JavaScript,并为其添加了强大的静态类型系统,使得代码在开发阶段就能进行更严格的类型检查,从而减少运行时错误,提高代码的质量和可维护性。无论是大型企业级应用还是小型的前端项目,TypeScript 都展现出了独特的优势。在这篇博客中,我们将深入探讨 TypeScript 的各个方面,包括它的安装、类型系统、接口、交叉类型、断言、泛型、装饰器和重载等特性,让你全面了解这一强大的编程工具。
二、TypeScript 的安装与初体验
(一)安装 TypeScript
要开始使用 TypeScript,首先需要在本地环境中安装它。使用 Node Package Manager(npm)可以轻松完成安装。在命令行中执行以下命令:
npm install -g typescript
这条命令会在全局环境中安装 TypeScript。安装完成后,可以通过以下命令检查 TypeScript 的版本:
tsc -v
这一步确保 TypeScript 已经正确安装在你的系统中,为后续的开发工作做好准备。
三、TypeScript 的类型系统
(一)基本类型
TypeScript 提供了丰富的基本类型,包括boolean
、string
、number
、array
、null
、undefined
和object
。这些基本类型构成了 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<类型>
的方式来声明。而null
和undefined
在 TypeScript 中有明确的类型定义,它们与其他类型的使用方式有所不同。
(二)元组(Tuple)
元组是一种特殊的数据类型,它允许我们表示一个已知元素数量和类型的数组,每个元素的类型可以不同。
let tupleType: [string, number] = ['hello', 10];
在这个例子中,tupleType
是一个包含一个字符串和一个数字的元组。元组在处理一些具有特定结构的数据时非常有用,例如函数返回多个不同类型的值时,可以使用元组来接收。
(三)枚举(Enum)
枚举是一种将一组相关的命名常量组织在一起的方式,它为代码增加了可读性和可维护性。
- 数字枚举
数字枚举默认从 0 开始依次递增。
enum Color {Red, Green, Blue};
let color: Color = Color.Green;
在这个例子中,Color
枚举定义了三个颜色常量,Red
的值为 0,Green
的值为 1,Blue
的值为 2。
- 字符串枚举
除了数字枚举,还可以定义字符串枚举。
enum Color {Red = 'red', Green = 'green', Blue = 'blue'};
let color: Color = Color.Green;
字符串枚举使得每个枚举成员的值都明确指定为一个字符串,这种方式在代码即文档方面有很大优势,代码的可读性更强。
(四)特殊类型:any、unknown、void 和 never
- any 类型
any
类型是一种可以代表任意类型的值。它允许我们绕过类型检查,但过度使用any
可能会导致类型系统的优势丧失。
let anyValue: any; // 任意类型 绕过 类型检查
anyValue = 'hello';
anyValue = 123;
anyValue = true;
anyValue = {};
- unknown 类型
unknown
类型表示一个未知类型的值。与any
不同,unknown
类型更加严格,在使用unknown
类型的值时,需要先进行类型检查或断言。
let unknownValue: unknown;
unknownValue = 'hello';
// 以下代码会报错,因为 unknown 类型的值不能直接调用方法
// unknownValue.trim();
unknownValue = 123;
unknownValue = true;
unknownValue = {};
-
any 和 unknown 的区别
虽然any
和unknown
都可以绕过类型检查,但它们的严格程度不同。any
可以赋值给任意类型,而unknown
只能赋值给自身和any
类型。 -
void 类型
void
类型通常用于声明函数的返回值类型为空。
function warnUser(): void {console.log('This is a warning message');
}
- never 类型
never
类型用于声明函数永远不会返回值,例如抛出异常的函数。
function error(message: string): never {throw new Error(message);
}
void
和never
的区别在于,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
接口定义了name
和time
两个属性,classObj
对象实现了这个接口,它必须包含name
和time
属性,并且类型要与接口中定义的一致。
(二)接口的特性
- 只读属性(readonly)和可选属性(?)
接口可以定义只读属性和可选属性。只读属性在初始化后不能被修改,可选属性则表示该属性在实现接口时可以存在也可以不存在。
interface Point {readonly x: number; // readonly 只读属性readonly y: number;z?: number; // 可选属性
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
- 面试:readonly vs const
const
和readonly
都可以用于声明常量,但它们有一些区别。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
- 可添加性
接口还可以定义任意属性,使用[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
类型是A
和B
类型的交叉,ab
对象必须同时满足A
和B
接口的定义。
(二)更复杂的交叉类型示例
交叉类型可以用于更复杂的场景,例如多个接口之间的交叉。
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
类型是A
、B
和C
三个接口的交叉,abc
对象需要满足所有相关接口对于x
属性的定义,虽然这个例子比较复杂,但展示了交叉类型在处理复杂类型关系时的强大能力。
(三)type(类型别名)vs interface(类型描述)
-
相同点
两者都可以声明类型,并且都具有一定的可拓展性。 -
不同点
- 声明次数:
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
在这种情况下,由于A
和B
对b
属性的类型定义不一致,且没有共同的类型满足这两个接口,所以b
的类型被推断为never
。
六、类型断言
(一)类型断言的概念
类型断言是一种告诉 TypeScript 编译器我们比它更了解某个值的类型的方式,它允许我们将一个值视为特定的类型,从而实现类型变量的多样化。
- as 语法
使用as
关键字进行类型断言。
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
- 尖括号语法(在某些情况下可能受限)
在某些环境中,也可以使用尖括号的方式进行类型断言,但在 React 等一些 JavaScript 框架中,尖括号可能会与 JSX 语法冲突,所以as
语法更为常用。
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
- 非空断言
在处理可能为undefined
或null
的值时,可以使用非空断言。
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
属性,从而判断person
是Teacher
类型还是Person
类型。这种类型守卫的方式使得我们可以在处理联合类型的值时,根据不同的类型执行相应的逻辑。
七、泛型
(一)泛型的概念
泛型是 TypeScript 中一个非常强大的特性,它主要解决类、接口、方法的复用性问题,以及对不特定数据类型的支持。泛型允许我们编写可以在多种类型上工作的代码,而不是针对特定类型编写重复的代码。
function getData<T, U>(value: T, score: U): T {return value;// 类型推断
}
let data = getData<string, number>('hello', 123);
在getData
函数中,T
和U
是泛型类型参数。当我们调用getData
函数时,可以指定T
和U
的具体类型,这样函数就可以根据传入的类型进行相应的类型检查和操作。同时,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
装饰器函数则可以用于修改类的属性的描述符,例如添加setter
和getter
方法。通过使用装饰器,我们可以在不修改类的原始代码的情况下,为类添加新的功能。
(二)装饰器的应用场景
装饰器在许多场景中都有广泛的应用,比如日志记录、性能监测、权限验证等。例如,在一个 Web 应用程序中,可以使用装饰器来记录某个方法的调用时间,或者检查用户是否有执行某个方法的权限。通过将这些功能封装在装饰器中,可以使代码更加清晰和可维护,将业务逻辑与这些额外的功能分离开来。
相关文章:
深入探索 TypeScript:从基础到高级特性
深入探索 TypeScript:从基础到高级特性 一、引言 在现代软件开发领域,TypeScript 已经成为了一种极具影响力的编程语言。它基于 JavaScript,并为其添加了强大的静态类型系统,使得代码在开发阶段就能进行更严格的类型检查&#x…...

Leetcode:118. 杨辉三角——Java数学法求解
题目——Leetcode:118. 杨辉三角 给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 示例 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,如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负。 ✍🏻作者简介:致…...

单元测试、集成测试、系统测试、验收测试、压力测试、性能测试、安全性测试、兼容性测试、回归测试(超详细的分类介绍及教学)
目录 1.单元测试 实现单元测试的方法: 注意事项: 2.集成测试 需注意事项: 实现集成测试的方法: 如何实现高效且可靠的集成测试: 3.系统测试 实现系统测试的方法: 须知注意事项: 4.验收测试 实现验…...
低代码集成多方API的简单实现
在现代软件开发中,集成多个API服务提供商已成为常见需求。然而,不同的API认证机制和数据格式使得集成过程变得复杂且耗时。为了应对这些挑战,本文将介绍一种低代码解决方案,通过配置化管理和简化的代码逻辑,帮助开发者…...

【测试框架篇】单元测试框架pytest(1):环境安装和配置
一、pytest简介 Pytest是Python的一种单元测试框架,与Python自带的unittest测试框架类似,但是比 unittest框架使用起来更简洁,效率更高。 二、pytest特点 Pytest是一个非常成熟的Python测试框架,主要特点有以下几点: 非常容易…...
Python数据分析NumPy和pandas(二十九、其他Python可视化工具)
与其他开源工具一样,在 Python 中创建图形有很多选项(太多了,无法一一列举)。自 2010 年以来,主要开发工作集中在创建用于在 Web 上发布交互式图形上。例如: Altair、Bokeh 和 Plotly 等工具,可…...

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. 文件上传的扩展:多文件上传与权限控制结语 前言 随着互联网应用的快速发展,…...

Python学习26天
集合 # 定义集合 num {1, 2, 3, 4, 5} print(f"num:{num}\nnum数据类型为:{type(num)}") # 求集合中元素个数 print(f"num中元素个数为:{len(num)}") # 增加集合中的元素 num.add(6) print(num) # {1,2,3,4,5,6} # 删除…...
linux startup.sh shutdown.sh (kkFileView)
linux启动脚本和关闭脚本startup.sh shutdown.sh (kkFileView) startup.sh DIR_HOME("/opt/openoffice.org3" "/opt/libreoffice" "/opt/libreoffice6.1" "/opt/libreoffice7.0" "/opt/libreoffice7.1&q…...
[MySQL]隐式类型转换
安全等号 <> 如果有参数为NULL,则除了相等比较运算符(),比较的结果为null。对于 nullnull,结果为true。 在select语句中,使用 时,结果不会包含值为 null 的记录,但如果使用安全等号 <> 来…...
面经总结1
文章目录 如何保证批量请求失败,只弹出一个toast1使用计数器:2使用标志变量: 如何减少项目里的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) 报告中,每个性能…...

数据结构--数组
一.线性和非线性 线性:除首尾外只有一个唯一的前驱和后继。eg:数组,链表等。 非线性:不是线性的就是非线性。 二.数组是什么? 数组是一个固定长度的存储相同数据类型的数据结构,数组中的元素被存储在一…...
nrm的安装及使用
nrm的安装及使用 NRM(NPM Registry Manager)是一个用于快速切换npm(Node Package Manager)源的工具。npm是Node.js的包管理工具,用于安装、发布、管理Node.js包。由于网络原因,直接使用npm官方源ÿ…...

【MatLab手记】 --从0到了解超超超详过程!!!
文章目录 MatLab笔记一、命令行窗口二、变量命名规则三、数据类型1. 数字2. 字符与字符串3. 矩阵3.1 矩阵创建3.2 矩阵的修改和删除3.3 矩阵的拼接与重构重排3.4 矩阵的运算方法3.5 矩阵的下标 4. 元胞数组(类似数据容器)5. 结构体 四、逻辑与流程控制五…...

从零创建vue+elementui+sass+three.js项目
初始化: 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 登录到远程服务器之后,你无法直接使用 telnet 发送文件。telnet 是一个纯文本协议,不支持文件传输。要发送文件,你需要使用其他工具,如 scp 或 sftp。以下是使用这两种工具发送文件的方法: 使用 scp 发…...
Jtti:服务器总是自动重启怎么办?
服务器总是自动重启可能是由于多种原因引起的,包括硬件故障、软件问题、配置错误或环境因素。以下是一些常见原因和相应的解决方案: 1. 硬件问题 电源故障:电源供应不稳定或电源模块故障可能导致服务器重启。 解决方案:检查电源供…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...

Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...

恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...