【前端高频面试题--TypeScript篇】
🚀 作者 :“码上有前”
🚀 文章简介 :前端高频面试题
🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬
高频前端面试题--Vue3.0篇
- 什么是TypeScript?
- TypeScript数据类型
- TypeScript中命名空间与模块的理解和区别
- TypeScript支持的访问修饰符有哪些?
- TypeScript中的Declare关键字有什么作用?
- TypeScript 中 any 类型的作用是什么?
- TypeScript 中 any 滥用会有什么后果
- TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别?
- TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?
- TS中any和unknown有什么区别?
- TypeScript中never和void的区别?
- TypeScript 中可使用 String、Number、Boolean、Symbol、Object 等给类型做声明吗?
- Union Types 注意事项?
- TypeScript 类型兼容性的理解?
- tsconfig.json 中有哪些配置项信息?
- keyof 和 typeof 关键字的作用?
- 简述工具类型 Exclude、Omit、Merge、Intersection、Overwrite的作用
- TypeScript中的枚举
- TypeScript中的模块
- TS中的类是什么,如何定义?
- TS中的泛型是什么?
- Omit 类型有什么作用
- 类型守卫(Type Guards)是什么
- 索引类型是什么?有什么好处?
- TypeScript中的this有什么需要注意?
- TypeScript中的协变、逆变、双变和抗变是什么?
- 介绍TypeScript中的可选属性、只读属性和类型断言
什么是TypeScript?
TypeScript是一种开源的编程语言,它是JavaScript的一个超集。它添加了静态类型系统和一些新的语言特性,以提供更强大的开发工具和增强JavaScript的可维护性。
TypeScript由微软开发和维护,并在2012年首次发布。它建立在JavaScript之上,并通过添加类型注解、接口、类、模块和其他一些语言特性来扩展JavaScript的功能。
以下是TypeScript的一些主要特点:
-
静态类型系统: TypeScript引入了静态类型系统,开发者可以为变量、函数参数、函数返回值等添加类型注解,以指定其数据类型。这使得在开发过程中可以在编译阶段捕获类型错误,提供更好的代码智能提示和错误检查。
-
类型推断: TypeScript具有类型推断的能力,它能够根据变量的赋值情况自动推断其类型,减少了手动注解的需要。这在一些情况下可以简化代码,并且仍然提供了类型安全性。
-
面向对象编程: TypeScript支持类、接口、继承和其他面向对象编程的特性。这使得在开发大型应用程序时可以使用面向对象的设计模式和组织代码的方式。
-
ES6+支持: TypeScript支持ECMAScript(ES)6及以上版本的语法和功能,开发者可以使用箭头函数、模块、解构赋值等现代JavaScript特性。
-
工具和生态系统: TypeScript与现有的JavaScript工具和库能够很好地集成,包括编辑器(如Visual Studio Code)、构建工具(如Webpack)、测试框架和其他开发工具。此外,TypeScript拥有庞大的社区支持和丰富的第三方库生态系统。
通过使用TypeScript,开发者可以在JavaScript的基础上获得更好的代码可维护性、类型安全性和开发工具的支持。TypeScript适用于各种规模的应用程序开发,尤其适用于大型项目和团队协作。
TypeScript数据类型
TypeScript 支持以下基本数据类型:
-
布尔类型(boolean):表示逻辑值,可以是
true
或false
。 -
数字类型(number):表示数值,包括整数和浮点数。
-
字符串类型(string):表示文本数据,使用单引号或双引号括起来。
-
数组类型(array):表示具有相同类型的一组值,可以使用类型注解声明,如
number[]
表示由数字组成的数组。 -
元组类型(tuple):表示具有固定长度和特定顺序的元素组成的数组。每个元素可以具有不同的类型。
-
枚举类型(enum):表示具有命名值的一组相关常量。枚举成员可以具有数字或字符串值。
-
任意类型(any):表示任意类型的值,不进行类型检查。
-
空类型(void):表示没有返回值的函数的返回类型。
-
null 和 undefined 类型:表示变量可以具有 null 或 undefined 值。
-
never 类型:表示永远不会发生的值的类型,通常用于函数返回类型,表示函数永远不会返回。
-
对象类型(object):表示非原始类型,封装了多个键值对。
-
类型推断类型(type inference):TypeScript 可以根据上下文自动推断变量的类型。
此外,还可以使用联合类型(union)和交叉类型(intersection)来组合多个类型。
TypeScript 还支持用户自定义的类型,包括接口(interface),类(class),函数类型(function types)和类型别名(type alias)等。
这些数据类型提供了静态类型检查和类型推断的功能,以增加代码的可读性、可维护性和可靠性。
TypeScript中命名空间与模块的理解和区别
在TypeScript中,命名空间(Namespace)和模块(Module)是用于组织和封装代码的两种不同的概念。
命名空间(Namespace):
命名空间是一种在全局作用域下定义的容器,用于组织具有相似功能或目的的代码。命名空间可以包含变量、函数、类和其他命名空间等。通过使用命名空间,可以避免全局命名冲突,将相关的代码组织在一起。
命名空间的声明使用namespace
关键字,可以嵌套定义子命名空间。例如:
namespace MyNamespace {
export const myVariable = 10;
export function myFunction() {
// ...
}
export namespace SubNamespace {
// ...
}
}
在使用命名空间中的成员时,需要使用带有命名空间前缀的限定符,例如MyNamespace.myVariable
。
模块(Module):
模块是一种将代码封装成可重用、独立的单元的方式。模块中的代码默认是封闭的,不会污染全局命名空间。模块可以包含变量、函数、类和其他模块等。模块使用export
关键字暴露可供外部访问的成员,使用import
关键字引入其他模块的成员。
模块可以使用不同的模块系统(如CommonJS、AMD、ES6模块)来加载和导入模块。
例如,使用ES6模块系统:
// moduleA.ts
export const myVariable = 10;
export function myFunction() {
// ...
}
// moduleB.ts
import { myVariable, myFunction } from './moduleA';
console.log(myVariable);
myFunction();
区别:
-
作用域: 命名空间的作用域是全局的,而模块的作用域是封闭的。模块中的成员默认是私有的,需要使用
export
关键字明确指定可供外部访问的成员。 -
加载和导入: 命名空间的成员需要使用限定符访问,而模块使用
import
关键字来导入其他模块的成员。 -
文件组织: 命名空间是通过文件组织来实现的,一个文件可以定义一个命名空间。而模块可以跨越多个文件,一个模块可以包含多个文件。
-
模块系统: 模块使用不同的模块系统来加载和导入模块,如CommonJS、AMD、ES6模块等。而命名空间没有与特定的模块系统相关联。
通常情况下,推荐使用模块来组织和封装代码,因为模块提供了更好的封装性、可扩展性和可重用性。命名空间适用于旧版本的JavaScript和特定的场景,如在全局命名空间中定义一些全局的工具函数或常量等。
TypeScript支持的访问修饰符有哪些?
TypeScript支持以下几种访问修饰符:
-
public(默认): public是默认的访问修饰符,如果没有显式指定访问修饰符,则成员被视为public。public成员可以在类内部和外部访问。
-
private: private修饰符限制了成员只能在类内部访问。私有成员不能被类外部的代码访问,包括子类。
-
protected: protected修饰符与private类似,但有一个区别。protected成员可以在类内部和子类中访问,但不能在类的实例或类外部访问。protected成员允许子类继承和访问基类的受保护成员。
-
readonly: readonly修饰符表示成员只读,不能被修改。它可以应用于属性和参数,但不能应用于方法。readonly成员必须在声明时或构造函数内进行初始化。
-
static: static修饰符用于定义静态成员。静态成员属于类本身,而不是类的实例。静态成员可以通过类名直接访问,而无需创建类的实例。
这些访问修饰符可以用于类的属性、方法和构造函数参数。它们提供了对类成员的访问控制和灵活性,帮助开发者组织和保护代码。
TypeScript中的Declare关键字有什么作用?
在TypeScript中,declare
关键字用于声明全局变量、全局函数、全局类或全局命名空间的定义。它通常用于描述那些已存在于JavaScript运行环境中的实体,但在TypeScript代码中无法获取其类型定义的情况。通过使用declare
关键字,开发者可以告诉TypeScript编译器某个变量、函数、类或命名空间已经存在,并且可以在代码中使用它们。
使用declare
关键字时,开发者并不需要提供具体的实现或定义,而是仅仅提供类型信息。这样在编译时,TypeScript编译器会假设这些声明已经存在,并且不会对它们进行编译或转译。
以下是一些使用declare
关键字的示例:
声明全局变量:
declare const myGlobalVariable: string;
声明全局函数:
declare function myGlobalFunction(arg: number): void;
声明全局类:
declare class MyClass {
constructor(arg: string);
method(): void;
}
声明全局命名空间:
declare namespace MyNamespace {
const myVariable: number;
function myFunction(): void;
}
这些declare
声明可以放在TypeScript的任何位置,通常放置在.d.ts
文件(类型声明文件)中,或者在TypeScript项目的某个全局声明文件中。通过使用declare
关键字,开发者可以与现有的JavaScript库或框架进行无缝集成,并在TypeScript代码中正确地引用和使用这些全局实体。
TypeScript 中 any 类型的作用是什么?
在TypeScript中,any
类型是一种表示任意类型的特殊类型。使用any
类型可以绕过类型检查器的类型检查,允许变量在编译时可以持有任何类型的值,并且可以进行任意的操作和赋值操作,而不会提示类型错误。
any
类型的作用主要有以下几个方面:
-
动态类型:
any
类型可以用于表示动态类型或未知类型的值。当我们不确定变量的具体类型时,可以将其标记为any
类型,以便在后续操作中灵活地处理各种类型的值。 -
与现有的JavaScript代码集成: 在将现有的JavaScript代码迁移到TypeScript时,可能会遇到一些无法确定类型的情况。使用
any
类型可以暂时将这些变量标记为any
,以便在不改变现有逻辑的情况下进行平滑的迁移。 -
临时解决方案: 在开发过程中,有时可能会遇到某些类型难以推断或引入复杂的类型声明的情况。在这种情况下,可以将变量标记为
any
类型作为临时解决方案,以便继续进行开发,并在后续逐步优化和添加类型注解。
然而,过度使用any
类型可能会导致类型安全性下降,因为它会绕过TypeScript的类型检查器,使编译器无法捕获潜在的类型错误。因此,在编写TypeScript代码时,应该尽量避免过度使用any
类型,并努力使用更具体的类型注解来提高代码的可读性和可维护性。
TypeScript 中 any 滥用会有什么后果
在 TypeScript 中滥用 any
类型可能会导致以下后果:
-
缺乏类型安全性:使用
any
类型会绕过 TypeScript 的类型检查机制,使类型错误不被捕获。这可能导致运行时错误,因为无法在编译阶段发现潜在的类型不匹配问题。 -
代码可读性下降:使用
any
类型会模糊代码的意图,使代码的含义和行为变得不清晰。阅读和理解代码会变得更加困难,特别是对于其他开发人员来说。 -
缺少自动化工具的支持:使用
any
类型会减少编辑器和其他自动化工具的能力来提供准确的代码补全、类型检查和重构支持。这也会降低开发效率。 -
代码维护困难:使用
any
类型的代码通常难以维护,特别是在较大的代码库中。由于缺乏类型信息,调试和修改代码可能会变得更加困难,增加了引入和隐藏错误的风险。 -
不利于团队协作:如果项目中的多个开发人员都滥用
any
类型,那么代码库的一致性和可维护性将受到威胁。团队成员可能会有不同的理解和使用方式,导致代码库的混乱和不一致。
虽然在某些情况下使用 any
类型可能是必要的,例如与第三方库集成或处理动态类型的情况,但滥用它会削弱 TypeScript 的主要优势之一——静态类型检查。因此,应该尽量避免滥用 any
类型,而是明确指定更具体的类型或使用其他 TypeScript 提供的类型工具来增强代码的类型安全性和可读性。
TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别?
const 和 readonly 的区别:
const
用于声明常量,常量的值在声明后不能被修改。readonly
用于修饰类的成员(属性或方法),表示其只读特性。readonly
修饰的成员在类的实例化后不可被修改。
区别总结如下:
const
用于声明常量,readonly
用于修饰类的成员。const
用于变量声明,readonly
用于类成员声明。const
值在声明时确定,readonly
值在运行时确定。const
用于任何作用域,readonly
用于类的成员。
枚举和常量枚举的区别:
- 枚举(Enum)是一种数据类型,用于定义一组具名的常量值。枚举成员具有名称和关联的值。
- 常量枚举(Const Enum)是一种特殊类型的枚举,它在编译时被内联,而不会生成真实的对象。
区别总结如下:
- 枚举在运行时存在,常量枚举在编译时被内联。
- 枚举成员可以包含计算值,常量枚举成员必须具有常量表达式。
- 枚举可用于类型注解,常量枚举不能用于类型注解。
- 常量枚举在编译后不会生成真实的对象,因此不能通过枚举值来访问。
接口和类型别名的区别:
- 接口(Interface)用于定义对象的结构和行为,它可以描述对象的属性、方法和索引签名等。接口可以被类、函数和其他接口实现。
- 类型别名(Type Alias)用于创建类型别名,它可以为现有的类型创建别名,使代码更加可读和可维护。类型别名可以表示任何类型,包括基本类型、联合类型、交叉类型和对象类型等。
区别总结如下:
- 接口用于描述对象的结构和行为,类型别名用于创建类型别名。
- 接口可以被类、函数和其他接口实现,类型别名不能。
- 接口可以被继承和实现,类型别名不能。
- 接口可以定义多个同名的成员,类型别名不能。
在实际使用中,接口通常用于描述对象的形状,类型别名通常用于创建复杂的类型别名或联合类型。选择使用接口还是类型别名取决于具体的需求和语境。
TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?
在TypeScript中,any
、never
、unknown
、null
& undefined
和void
是不同的类型,它们具有以下区别:
-
any
:any
类型表示任意类型。使用any
类型时,变量可以持有任何类型的值,而且可以对其进行任何操作,绕过类型检查。它提供了灵活性,但也降低了类型安全性。应尽量避免过多地使用any
类型,以充分发挥TypeScript的类型检查功能。 -
never
:never
类型表示永远不会发生的类型,通常用于函数的返回类型,表示函数不会正常返回或抛出异常。例如,一个抛出异常的函数的返回类型可以为never
。 -
unknown
:unknown
类型表示未知类型。与any
不同,unknown
是一种类型安全的未知类型。变量被标记为unknown
类型后,不能直接对其进行任何操作,除非使用类型断言或类型检查。可以将unknown
类型的值赋给任何其他类型的变量,但赋值后仍然需要进行类型检查。 -
null
和undefined
:null
和undefined
是JavaScript中的特殊值,表示空或未定义的值。在TypeScript中,它们是各自的类型。null
类型只能赋值为null
,而undefined
类型只能赋值为undefined
。默认情况下,它们是所有其他类型的子类型。 -
void
:void
类型表示没有任何类型,通常用于函数的返回类型,表示函数没有返回值。例如,一个不返回任何值的函数的返回类型可以为void
。变量声明为void
类型时,只能赋值为undefined
或null
。
总结:
any
表示任意类型,never
表示永远不会发生,unknown
表示未知类型,null
和undefined
是特殊值的类型,void
表示没有任何类型。any
是最灵活的类型,unknown
是安全的未知类型,void
表示没有返回值,never
表示永远不会结束。- 尽量避免过多地使用
any
类型,而是尽可能使用更具体的类型注解来提高类型安全性。
TS中any和unknown有什么区别?
在 TypeScript 中,any
和 unknown
都是用于处理不确定类型的关键字,但它们有一些区别。
-
any
类型:any
表示任意类型,使用any
关键字可以使一个变量可以接受任何类型的值,即取消了类型检查。例如:let variable: any = 42; variable = "hello"; variable = true;
在上面的示例中,
variable
被声明为any
类型,可以在不同的赋值语句中接受不同类型的值,因为它不会进行类型检查。使用any
类型可以方便地处理不确定类型的值,但也会失去 TypeScript 提供的类型安全性。 -
unknown
类型:unknown
表示未知类型,使用unknown
关键字声明的变量需要进行类型检查或类型断言,才能进行具体的操作。例如:let variable: unknown = 42; let value: number = variable as number;
在上面的示例中,我们将变量
variable
声明为unknown
类型,然后使用类型断言将其转换为number
类型,并将其赋值给value
变量。使用unknown
类型时,需要进行类型检查或类型断言,以确保安全操作。与
any
不同,unknown
类型在使用之前需要进行类型检查或类型断言,这有助于在编译时捕获潜在的类型错误,并提供更严格的类型安全。
总结:
any
类型表示任意类型,取消了类型检查,可以接受任何类型的值。unknown
类型表示未知类型,需要进行类型检查或类型断言才能进行具体操作,提供了更严格的类型安全。
推荐尽可能避免使用 any
类型,而是使用更精确的类型注解或使用 unknown
类型来提高类型安全性。
TypeScript中never和void的区别?
在 TypeScript 中,never
和 void
是两个表示不同类型的关键字。
-
void
类型:void
表示没有返回值的类型。当一个函数没有返回值时,可以将其返回类型标注为void
。例如:function logMessage(message: string): void { console.log(message); } function doSomething(): void { // 执行一些操作,但没有返回值 }
在上面的示例中,
logMessage
函数用于打印一条消息,它的返回类型被标注为void
,因为它没有返回任何值。同样,doSomething
函数也被标注为void
,因为它没有返回值。 -
never
类型:never
表示那些永远不会发生的类型。它通常用于表示抛出异常或无法正常结束的函数的返回类型。例如:function throwError(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) { // 执行一些操作,但无法正常结束循环 } }
在上面的示例中,
throwError
函数用于抛出一个错误,它的返回类型被标注为never
,因为函数在抛出错误后不会继续执行。同样,infiniteLoop
函数也被标注为never
,因为它陷入一个无限循环,无法正常结束。
总结:
void
表示没有返回值的类型,用于函数没有返回值的情况。never
表示那些永远不会发生的类型,用于表示抛出异常或无法正常结束的函数的返回类型。
需要注意的是,void
是一种有效的类型,可以将变量声明为 void
类型,而 never
是一种底部类型(bottom type),不是有效的类型,不能将变量声明为 never
类型。
TypeScript 中可使用 String、Number、Boolean、Symbol、Object 等给类型做声明吗?
在TypeScript中,可以使用String
、Number
、Boolean
、Symbol
和Object
等类型名称来声明类型,但需要注意以下几点:
-
String
、Number
和Boolean
:这些类型名称表示对应的 JavaScript 原始类型的包装对象。在 TypeScript 中,可以使用小写的string
、number
和boolean
来表示相应的原始类型。例如:let str: string = "Hello"; let num: number = 42; let bool: boolean = true;
-
Symbol
:Symbol
类型用于表示唯一的标识符。在 TypeScript 中,可以使用symbol
小写来表示Symbol
类型。例如:const sym: symbol = Symbol("key");
-
Object
:Object
类型表示非原始类型的值,即除了string
、number
、boolean
、symbol
和null
、undefined
之外的类型。在 TypeScript 中,可以使用小写的object
来表示Object
类型。例如:let obj: object = { key: "value" };
需要注意的是,使用小写的 string
、number
、boolean
、symbol
和object
来表示类型时,它们是 TypeScript 中的关键字,而不是 JavaScript 中的类型。这些关键字在 TypeScript 中用于表示对应的类型,并提供了类型检查和静态类型推断的功能。
Union Types 注意事项?
在 TypeScript 中使用联合类型(Union Types)时,有一些注意事项需要注意:
-
类型兼容性: 联合类型允许变量可以是多个类型之一,但在使用联合类型时要注意类型兼容性。如果一个变量被声明为联合类型,那么在使用该变量时,只能访问联合类型中所有类型的共有成员。如果访问的成员是联合类型中某个类型独有的成员,会导致类型错误。
-
类型缩窄(Type Narrowing): 在使用联合类型时,可以使用类型缩窄的技巧来缩小变量的类型范围。通过类型断言、类型保护(如类型谓词函数、typeof 和 instanceof 运算符)或条件语句等方式,可以在特定的代码块中确定变量的具体类型,并针对该类型进行操作。
-
使用类型保护: TypeScript 提供了一些用于类型保护的语法和特性,如类型谓词函数、typeof 和 instanceof 运算符。使用这些特性可以在运行时检查变量的类型,并根据类型进行相应的操作,避免潜在的类型错误。
-
处理联合类型的值: 当使用联合类型的值时,要确保对所有可能的类型进行处理。可以使用条件语句、类型断言或类型保护来处理联合类型的值,并确保代码在处理每个类型时都是正确的。
-
可辨识联合(Discriminated Unions): 可辨识联合是一种使用联合类型和字面量类型的模式,通过共享一个公共的可辨识属性(Discriminant Property)来区分不同的类型。这种模式可以在处理联合类型时提供更强大的类型推断和类型安全性。
总之,使用联合类型时需要注意类型兼容性、类型缩窄、类型保护和对联合类型的值进行全面处理。合理使用联合类型可以增强代码的灵活性和可读性,但也需要小心处理,以确保类型安全和代码健壮性。
TypeScript 类型兼容性的理解?
TypeScript 的类型兼容性是指在类型系统中判断一个类型是否可以被赋值给另一个类型的规则。它是 TypeScript 的核心特性之一,用于确保类型的一致性和类型安全性。
在 TypeScript 中,类型兼容性是基于结构子类型(Structural Subtyping)的原则,而不是基于名义类型(Nominal Typing)。结构子类型是指当两个类型的结构(成员和属性)相互兼容时,它们被认为是兼容的,即使它们的类型名称不同。
类型兼容性规则如下:
-
赋值兼容性(Assignment Compatibility): 当把一个类型的值赋值给另一个类型时,如果目标类型包含了源类型的所有必需属性,并且属性的类型兼容,那么源类型就可以赋值给目标类型。
-
函数兼容性(Function Compatibility): 当比较函数的参数类型和返回值类型时,目标函数的参数类型必须兼容源函数的参数类型,并且源函数的返回值类型必须兼容目标函数的返回值类型。
-
可选属性和任意属性的兼容性: TypeScript 中的可选属性和任意属性(索引签名)对兼容性有一定影响。如果目标类型具有额外的可选属性或任意属性,并且源类型的属性在目标类型中都存在(或可以匹配到任意属性类型),那么源类型就可以赋值给目标类型。
-
联合类型的兼容性: 对于联合类型,只要源类型兼容联合类型的所有类型之一,就可以赋值给联合类型。
需要注意的是,类型兼容性是双向的,即如果 A 兼容 B,那么 B 也兼容 A。同时,TypeScript 具有类型推断的能力,在某些情况下可以根据上下文自动推断类型,进一步提高类型兼容性。
通过理解 TypeScript 的类型兼容性规则,我们可以更好地利用 TypeScript 的类型系统,编写类型安全且灵活的代码。
tsconfig.json 中有哪些配置项信息?
TypeScript 的 tsconfig.json
文件是用于配置 TypeScript 编译器的选项和设置的。以下是一些常见的配置项信息:
-
compilerOptions
(编译器选项):这是一个包含编译器选项的对象。一些常见的编译器选项包括:target
:指定编译后的 JavaScript 目标版本。module
:指定生成的模块化代码的模块系统。outDir
:指定编译输出的目录。strict
:启用严格的类型检查。noImplicitAny
:禁止隐式的any
类型。esModuleInterop
:启用更简洁的模块导入语法。sourceMap
:生成相应的源映射文件。declaration
:生成相应的声明文件(.d.ts
)。strictNullChecks
:启用对null
和undefined
的严格检查。
-
include
和exclude
:这两个属性用于指定要包含和排除的文件或目录的匹配规则。可以使用 glob 模式进行匹配。 -
files
:指定要包含的特定文件的相对或绝对路径列表,而不是使用include
和exclude
进行匹配。 -
extends
:指定要继承的另一个tsconfig.json
文件的路径。可以继承其他配置文件的设置。 -
compilerOptions
之外的其他配置项:除了compilerOptions
之外,tsconfig.json
文件还可以包含其他自定义的配置项,供构建脚本或工具使用。
这只是 tsconfig.json
文件中的一些常见配置项信息,还有其他一些配置项可以根据项目的需求进行配置。完整的配置选项可以在 TypeScript 官方文档中找到,详细说明了每个选项的作用和可用值。
请注意,tsconfig.json
文件中的配置项并非全部必需,可以根据项目的需求进行选择和配置。
keyof 和 typeof 关键字的作用?
keyof
和 typeof
是 TypeScript 中的两个关键字,用于获取类型信息和进行类型操作:
-
keyof
运算符:keyof
运算符用于获取一个类型的所有属性名组成的联合类型。它可以用于访问一个类型的属性名称,然后在类型中使用这些属性名称进行进一步的操作。例如:type Person = { name: string; age: number; }; type PersonKeys = keyof Person; // "name" | "age"
在上面的示例中,
PersonKeys
类型是"name"
和"age"
的联合类型。 -
typeof
运算符:typeof
运算符用于获取一个值或表达式的类型。它可以用于获得变量、函数、类或表达式的类型。例如:const x = 10; type XType = typeof x; // number
在上面的示例中,
XType
类型被推断为number
,因为变量x
的类型是number
。typeof
运算符还可以用于获取函数的参数类型和返回值类型。例如:function greet(name: string): string { return "Hello, " + name; } type GreetParams = Parameters<typeof greet>; // [string] type GreetReturn = ReturnType<typeof greet>; // string
在上面的示例中,
GreetParams
类型是一个包含string
类型的元组类型,表示函数greet
的参数类型。GreetReturn
类型表示函数greet
的返回值类型,被推断为string
。
keyof
和 typeof
关键字在 TypeScript 中提供了一些元编程的能力,可以在编译时获取类型信息,并进行类型操作和推断。它们在许多高级类型和泛型场景中非常有用。
简述工具类型 Exclude、Omit、Merge、Intersection、Overwrite的作用
这里是对这些 TypeScript 工具类型的简要描述:
-
Exclude<T, U>
:Exclude
工具类型用于从类型T
中排除可以赋值给类型U
的成员。它返回一个新的类型,其中包含了T
中不属于U
的成员。例如:type MyType = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
-
Omit<T, K>
:Omit
工具类型用于从类型T
中排除指定的属性K
。它返回一个新的类型,其中包含了T
中除了K
属性之外的所有属性。例如:type MyType = Omit<{ a: number; b: string; c: boolean }, "a">; // { b: string; c: boolean }
-
Merge<T, U>
:Merge
工具类型用于将两个类型T
和U
进行合并。它返回一个新的类型,其中包含了T
和U
的所有成员。如果有重名的属性,则属性类型将被合并为联合类型。例如:type Type1 = { a: number; b: string }; type Type2 = { b: number; c: boolean }; type MyType = Merge<Type1, Type2>; // { a: number; b: number; c: boolean }
-
Intersection<T, U>
:Intersection
工具类型用于获取类型T
和U
的交集。它返回一个新的类型,其中包含了T
和U
共有的成员。例如:type Type1 = { a: number; b: string }; type Type2 = { b: number; c: boolean }; type MyType = Intersection<Type1, Type2>; // { b: string }
-
Overwrite<T, U>
:Overwrite
工具类型用于将类型T
中的属性与类型U
中的对应属性进行覆盖。它返回一个新的类型,其中包含了T
和U
的所有成员,但T
中的属性被U
中的属性所覆盖。例如:type Type1 = { a: number; b: string }; type Type2 = { b: number; c: boolean }; type MyType = Overwrite<Type1, Type2>; // { a: number; b: number; c: boolean }
这些工具类型在 TypeScript 中提供了强大的类型操作和转换功能,可以帮助我们更灵活地处理和组合类型。它们广泛应用于泛型和高级类型场景中,帮助我们进行类型的过滤、合并、重写等操作,使代码更具表达力和可读性。
TypeScript中的枚举
在 TypeScript 中,枚举(Enum)是一种用于定义命名常量集合的数据类型。它允许我们为一组相关的值分配易于理解的名称,并且可以更加类型安全地使用这些值。
下面是一个简单的枚举示例:
enum Direction {
Up,
Down,
Left,
Right
}
在上面的示例中,我们定义了一个名为 Direction
的枚举,它包含了四个成员:Up
、Down
、Left
和 Right
。这些成员被称为枚举常量,它们在枚举内部被赋予默认的数字值。默认情况下,第一个成员的值为 0
,后续成员的值依次递增。
我们可以通过枚举成员的名称来引用它们的值,例如:
let userDirection: Direction = Direction.Up;
if (userDirection === Direction.Up) {
console.log("User is going Up");
}
在上面的示例中,我们声明了一个变量 userDirection
,并将其赋值为 Direction.Up
。然后,我们使用 if
语句检查 userDirection
是否等于 Direction.Up
,如果是,则输出相应的消息。
除了默认的数字值分配,枚举还支持手动指定值,如下所示:
enum Color {
Red = 1,
Green = 2,
Blue = 4
}
在上面的示例中,我们手动指定了每个枚举成员的值。这样,Color.Red
的值为 1
,Color.Green
的值为 2
,Color.Blue
的值为 4
。
枚举在 TypeScript 中还有其他一些特性,例如反向映射(可以通过枚举值获取枚举成员名称)、常量枚举(在编译时被移除,只保留枚举值的使用)、字符串枚举(使用字符串值而不是数字值)等。可以在 TypeScript 官方文档中查阅更多关于枚举的详细信息和用法。
TypeScript中的模块
在 TypeScript 中,模块用于组织和封装代码,并提供了一种在不同文件中共享和重用代码的机制。模块可以包含变量、函数、类和其他 TypeScript 实体,并通过导出(export)和导入(import)语句进行访问。
下面是一些关于 TypeScript 模块的重要概念和用法:
-
模块的导出(Export): 在模块中,使用
export
关键字将变量、函数、类或其他实体导出,使其可以在其他模块中使用。例如:// moduleA.ts export const myVariable = 42; // moduleB.ts import { myVariable } from './moduleA'; console.log(myVariable); // 42
-
模块的默认导出(Default Export): 一个模块可以使用
export default
关键字来指定默认导出,一个模块只能有一个默认导出。默认导出可以是任何值,例如函数、类、对象等。例如:// moduleA.ts const myVariable = 42; export default myVariable; // moduleB.ts import myVariable from './moduleA'; console.log(myVariable); // 42
-
模块的导入(Import): 在模块中,使用
import
关键字将其他模块导入,以便在当前模块中使用导出的实体。可以使用命名导入和默认导入两种方式。例如:// moduleA.ts export const myVariable = 42; // moduleB.ts import { myVariable } from './moduleA'; console.log(myVariable); // 42
-
模块的重新导出(Re-export): 在一个模块中,可以使用
export
关键字重新导出来自其他模块的实体,以便在当前模块中通过单一导入语句访问多个实体。例如:// moduleA.ts export const myVariable = 42; // moduleB.ts export { myVariable } from './moduleA'; // moduleC.ts import { myVariable } from './moduleB'; console.log(myVariable); // 42
-
模块的路径解析(Module Resolution): TypeScript 提供了不同的模块解析策略,用于确定模块引用的位置。可以使用相对路径或基于配置的模块解析策略(如 Node.js 风格的模块解析)。例如:
// 使用相对路径 import { myVariable } from './moduleA'; // 使用基于配置的模块解析 import { myVariable } from 'my-module';
这些是 TypeScript 中模块的基本概念和用法。模块的使用可以帮助我们组织和管理代码,提高代码的可维护性和可重用性。在实际项目中,可以根据需要结合模块的导出、导入和重新导出等特性,构建模块化的 TypeScript 应用程序。
TS中的类是什么,如何定义?
在 TypeScript 中,类(Class)是一种面向对象编程的核心概念,用于创建具有相似属性和行为的对象。类提供了一种用于定义对象的模板或蓝图,它可以包含属性(成员变量)和方法(成员函数)。
以下是定义类的基本语法:
class ClassName {
// 成员变量
property1: type1;
property2: type2;
// 构造函数
constructor(param1: type1, param2: type2) {
this.property1 = param1;
this.property2 = param2;
}
// 成员函数
method1() {
// 执行一些操作
}
method2() {
// 执行一些操作
}
}
在上面的示例中,我们使用 class
关键字来定义一个类,并指定类的名称为 ClassName
。在类的内部,我们可以定义成员变量 property1
和 property2
,并指定它们的类型。我们还可以定义构造函数 constructor
,用于创建类的实例并初始化成员变量。类的成员函数(方法) method1
和 method2
用于定义类的行为。
类的实例化:
const obj = new ClassName(arg1, arg2);
在上面的示例中,我们使用 new
关键字创建了 ClassName
类的一个实例,并传递了构造函数所需的参数。
除了成员变量和成员函数,类还可以具有访问修饰符(如 public
、private
、protected
),用于控制成员的可访问性。还可以使用继承机制通过 extends
关键字创建子类(派生类)来扩展现有的类。
以下是一个包含继承和访问修饰符的类的示例:
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
protected eat() {
console.log(`${this.name} is eating.`);
}
}
class Dog extends Animal {
private breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
public bark() {
console.log(`${this.name} (${this.breed}) is barking.`);
}
}
const dog = new Dog("Buddy", "Labrador");
dog.bark(); // 输出: Buddy (Labrador) is barking.
在上面的示例中,Animal
类具有一个受保护的成员变量 name
和一个受保护的成员函数 eat
。Dog
类继承了 Animal
类,并添加了一个私有的成员变量 breed
和一个公共的成员函数 bark
。我们可以看到,子类可以访问父类的受保护成员。
通过定义类,我们可以创建对象并使用面向对象的编程风格来组织和管理代码。类提供了封装、继承和多态等面向对象编程的重要特性。
TS中的泛型是什么?
在 TypeScript 中,泛型(Generics)是一种允许在定义函数、类或接口时使用类型参数的特性。泛型可以用于创建可重用的组件,使其能够在多种类型上工作,提高代码的灵活性和复用性。
使用泛型,我们可以定义一个在未知具体类型下工作的函数或类,而在实际使用时,可以根据需要指定具体的类型。通过泛型,我们可以编写更通用、更灵活的代码。
以下是使用泛型的基本语法:
函数泛型:
function functionName<T>(param: T): T {
// 执行一些操作
return param;
}
在上面的示例中,<T>
表示泛型类型参数,我们可以在函数体内使用 T
来表示未知具体类型。在调用函数时,可以指定具体的类型,使函数适用于不同类型的参数。
类泛型:
class ClassName<T> {
private property: T;
constructor(param: T) {
this.property = param;
}
public method(): T {
// 执行一些操作
return this.property;
}
}
在上面的示例中,<T>
表示泛型类型参数,我们可以在类的成员变量、构造函数和成员函数中使用 T
来表示未知具体类型。在创建类的实例时,可以指定具体的类型,使类适用于不同类型的属性和方法。
泛型可以与 TypeScript 的类型约束一起使用,以确保特定类型的一致性或满足特定的接口要求。例如,可以使用泛型约束来限制泛型类型必须具有某些属性或方法。
以下是一个使用泛型约束的示例:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // 输出: 5
logLength([1, 2, 3]); // 输出: 3
logLength({ length: 10 }); // 输出: 10
在上面的示例中,Lengthwise
是一个具有 length
属性的接口。函数 logLength
使用泛型约束 <T extends Lengthwise>
,表示参数 arg
必须具有 length
属性。这样,我们可以确保在函数内部访问该属性而不会出现类型错误。
泛型是 TypeScript 中非常强大和有用的特性,它可以增强代码的类型安全性和可重用性。通过使用泛型,我们可以编写更通用、更灵活的函数和类,以适应不同类型的数据处理需求。
Omit 类型有什么作用
在 TypeScript 中,Omit
是一个预定义的工具类型,用于创建一个新类型,该新类型从另一个类型中排除指定的属性。
Omit
类型的作用是创建一个新类型,该新类型是原始类型的一个子集,但排除了指定的属性。通过使用 Omit
类型,可以方便地从一个类型中删除某些属性,得到一个新的类型。
Omit
类型的语法如下所示:
Omit<OriginalType, KeysToOmit>
其中:
OriginalType
是原始类型,即要从中删除属性的类型。KeysToOmit
是要从原始类型中删除的属性的名称,可以是单个属性名称或属性名称的联合。
以下是一个使用 Omit
类型的示例:
interface Person {
name: string;
age: number;
email: string;
}
type PersonWithoutEmail = Omit<Person, 'email'>;
const person: PersonWithoutEmail = {
name: 'John',
age: 30,
};
在上面的示例中,我们定义了一个 Person
接口,它具有 name
、age
和 email
属性。然后,我们使用 Omit
类型创建了一个名为 PersonWithoutEmail
的新类型,从 Person
类型中排除了 'email'
属性。最后,我们创建了一个 person
对象,它是 PersonWithoutEmail
类型的实例,只包含 name
和 age
属性。
通过使用 Omit
类型,我们可以方便地从现有类型中创建一个新类型,而无需手动定义所有属性。这在需要从现有类型中排除某些属性时非常有用,可以减少代码重复并提高代码的可维护性。
类型守卫(Type Guards)是什么
类型守卫(Type Guards)是 TypeScript 中的一种机制,用于在运行时检查变量的类型,并根据类型进行不同的操作或处理。类型守卫允许我们在代码中识别和处理不同类型的值,以确保类型安全性并执行相应的逻辑。
在 TypeScript 中,有几种方法可以实现类型守卫:
-
typeof
类型守卫:
使用typeof
运算符可以在运行时检查变量的类型。例如:function printValue(value: string | number) { if (typeof value === 'string') { console.log(value.toUpperCase()); } else { console.log(value.toFixed(2)); } }
-
instanceof
类型守卫:
使用instanceof
运算符可以检查对象是否是特定类的实例。例如:class MyClass { // ... } function processObject(obj: MyClass | string) { if (obj instanceof MyClass) { obj.doSomething(); } else { console.log(obj.length); } }
-
自定义类型谓词:
我们可以使用自定义类型谓词来定义一个函数,用于检查变量的类型。类型谓词是一个返回值为布尔类型的函数,它在函数体内部使用类型断言来告诉编译器变量的类型。例如:function isString(value: any): value is string { return typeof value === 'string'; } function processValue(value: string | number) { if (isString(value)) { console.log(value.toUpperCase()); } else { console.log(value.toFixed(2)); } }
类型守卫使我们能够使用更加精确的类型,从而在编译时和运行时提供更好的类型安全性。通过使用类型守卫,我们可以根据变量的类型执行特定的操作,避免类型错误和运行时异常。这对于处理联合类型和动态类型的情况特别有用。
索引类型是什么?有什么好处?
索引类型(Index Types)是 TypeScript 中一种强大的特性,它允许我们使用字符串或数字作为类型的索引,以动态地访问和操作类型的属性。
在 TypeScript 中,有三种主要的索引类型:字符串索引类型、数字索引类型和索引签名。
-
字符串索引类型:
字符串索引类型允许我们使用字符串作为类型的索引,以访问和操作类型的属性。使用字符串索引类型,我们可以通过字符串键名动态地获取或设置对象的属性。例如:interface Person { name: string; age: number; } const person: Person = { name: 'John', age: 30, }; function getProperty(obj: Person, key: string) { return obj[key]; } const name = getProperty(person, 'name'); // 类型为 string const age = getProperty(person, 'age'); // 类型为 number
-
数字索引类型:
数字索引类型允许我们使用数字作为类型的索引。与字符串索引类型类似,数字索引类型使我们能够动态地访问和操作类型的属性。例如:interface NumberArray { [index: number]: number; } const arr: NumberArray = [1, 2, 3]; const firstElement = arr[0]; // 类型为 number const secondElement = arr[1]; // 类型为 number
-
索引签名:
索引签名是一种在接口或类型别名中定义索引类型的方式。它使用字符串或数字索引来描述对象的属性类型。例如:interface Dictionary<T> { [key: string]: T; } const dict: Dictionary<number> = { a: 1, b: 2, c: 3, }; const valueA = dict['a']; // 类型为 number const valueB = dict['b']; // 类型为 number
索引类型的好处包括:
- 动态访问属性:使用索引类型,我们可以通过字符串或数字键动态地访问和操作类型的属性,而不需要提前定义所有可能的属性。
- 泛用性:索引类型使我们能够编写更通用和灵活的代码,适应不同的数据结构和需求。
- 类型安全性:通过使用索引类型,我们可以在编译时捕获一些错误,例如访问不存在的属性或使用错误的索引类型。
索引类型是 TypeScript 中的一个强大特性,它使我们能够编写更动态和灵活的代码,同时保持类型安全性。通过使用索引类型,我们可以在编译时获得更好的类型检查,并提高代码的可维护性和可重用性。
TypeScript中的this有什么需要注意?
在 TypeScript 中,this
关键字表示当前执行上下文中的对象。由于 JavaScript 中 this
的行为相对复杂且容易出错,TypeScript 提供了一些机制来帮助开发人员更好地处理 this
。
以下是在 TypeScript 中使用 this
时需要注意的几点:
-
函数中的
this
类型:
在函数中,可以使用函数类型中的this
类型来指定this
的类型。这可以确保在函数调用时,this
的类型被正确推断。例如:function sayHello(this: { name: string }) { console.log(`Hello, ${this.name}!`); } const person = { name: 'John' }; sayHello.call(person); // 正确推断 `this` 类型为 `{ name: string }`
-
箭头函数中的
this
:
箭头函数不会绑定自己的this
值,而是继承包围它的最近的非箭头函数的this
值。这意味着在箭头函数中使用this
时,它会自动获取外部作用域中的this
值。例如:const obj = { name: 'John', sayHello: function() { setTimeout(() => { console.log(`Hello, ${this.name}!`); // `this` 继承自外部作用域的 `this` }, 1000); } }; obj.sayHello(); // 输出:Hello, John!
-
显式绑定
this
:
可以使用bind
、call
或apply
方法显式地绑定函数的this
值。这允许在调用函数时指定要绑定的对象。例如:function sayHello() { console.log(`Hello, ${this.name}!`); } const person = { name: 'John' }; const boundSayHello = sayHello.bind(person); boundSayHello(); // 输出:Hello, John!
-
类中的
this
:
在类中,可以使用this
关键字来引用当前实例的成员。在类的方法中,默认情况下,this
的类型会被正确推断为类的实例类型。但是,在回调函数或异步函数中,需要小心处理this
的类型。可以使用箭头函数、绑定this
或使用this
类型来确保在类中的不同上下文中正确使用this
。
总而言之,要注意在 TypeScript 中正确处理 this
的类型和上下文。可以使用函数类型中的 this
类型、箭头函数和显式绑定来管理 this
的值。此外,在类中,需要特别注意回调函数和异步函数中的 this
类型,以确保正确引用类的实例成员。
TypeScript中的协变、逆变、双变和抗变是什么?
**在 TypeScript 中,协变(Covariance)、逆变(Contravariance)、双变(Bivariance)和抗变(Invariance)是与类型兼容性和函数参数类型关系相关的概念。
-
协变(Covariance):
协变是指一种类型关系,其中子类型的值可以被赋值给父类型。在 TypeScript 中,协变适用于函数返回类型及数组和泛型类型参数的赋值。例如:interface Animal { eat(): void; } interface Cat extends Animal { meow(): void; } let animal: Animal; let cat: Cat; animal = cat; // 协变:子类型 Cat 可以赋值给父类型 Animal
-
逆变(Contravariance):
逆变是指一种类型关系,其中父类型的值可以被赋值给子类型。在 TypeScript 中,逆变适用于函数参数类型的赋值。例如:interface Animal { eat(): void; } interface Cat extends Animal { meow(): void; } let animalFn: (animal: Animal) => void; let catFn: (cat: Cat) => void; animalFn = catFn; // 逆变:父类型 (animal: Animal) => void 可以赋值给子类型 (cat: Cat) => void
-
双变(Bivariance):
双变是指一种类型关系,其中子类型的值可以被赋值给父类型,同时父类型的值也可以被赋值给子类型。在 TypeScript 中,默认情况下,函数参数类型是双变的。例如:interface Animal { eat(): void; } interface Cat extends Animal { meow(): void; } let animalFn: (animal: Animal) => void; let catFn: (cat: Cat) => void; animalFn = catFn; // 双变:父类型 (animal: Animal) => void 可以赋值给子类型 (cat: Cat) => void catFn = animalFn; // 双变:子类型 (cat: Cat) => void 可以赋值给父类型 (animal: Animal) => void
-
抗变(Invariance):
抗变是指一种类型关系,其中子类型的值不能被赋值给父类型,也不能将父类型的值赋值给子类型。在 TypeScript 中,基本类型通常表现为抗变。例如:let number: number; let string: string; number = string; // 抗变:基本类型不能相互赋值
需要注意的是,函数参数类型在 TypeScript 中默认是双变的,这意味着可以将具有不同参数类型的函数赋值给彼此。然而,双变性会带来一些潜在的类型安全问题,因此在使用函数参数类型时需要谨慎。可以使用 --strictFunctionTypes
标志来启用更严格的函数参数类型规则,以避免一些潜在的问题。
理解协变、逆变、双变和抗变对于正确处理类型兼容性和函数参数类型非常重要,特别是在涉及复杂类型和函数传递的情况下。
介绍TypeScript中的可选属性、只读属性和类型断言
在 TypeScript 中,有三个常用的特性:可选属性、只读属性和类型断言。
-
可选属性(Optional Properties):
可选属性允许在对象类型中声明某些属性为可选的,即可以存在或不存在。在属性名后面添加问号?
表示该属性是可选的。例如:interface Person { name: string; age?: number; // 可选属性 } const person1: Person = { name: 'John', age: 25, }; const person2: Person = { name: 'Jane', };
在上述示例中,
age
属性是可选的,可以选择性地添加到Person
对象中。 -
只读属性(Readonly Properties):
只读属性用于将对象的属性设置为只读,即不能修改其值。在属性名前面添加readonly
关键字表示该属性是只读的。例如:interface Point { readonly x: number; // 只读属性 readonly y: number; } const point: Point = { x: 10, y: 20, }; point.x = 5; // 错误,只读属性不能被修改
在上述示例中,
x
和y
属性被设置为只读,一旦被初始化,就不能再修改其值。 -
类型断言(Type Assertion):
类型断言允许开发人员显式地指定一个值的类型,即告诉编译器某个变量的实际类型。有两种形式的类型断言:尖括号语法和 as 语法。例如:let someValue: any = 'Hello, TypeScript!'; let strLength1: number = (<string>someValue).length; // 尖括号语法 let strLength2: number = (someValue as string).length; // as 语法
在上述示例中,将
someValue
断言为string
类型,以便调用length
属性。
类型断言可以用于处理类型转换或在编译器无法推断出准确类型的情况下提供类型信息。
这些特性使得 TypeScript 更加灵活和强大,可以更好地描述和控制代码的结构和行为。
都看到这里了,点个赞吧🚀