对 TypeScript 的理解?与 JavaScript 的区别?
概念:
TypeScript
是 JavaScript 的类型的超集,⽀持 ES6
语法,⽀持⾯向对象编程的概念,如类、接⼝、继承、泛型等。
是⼀种静态类型检查的语⾔,提供了类型注解,在代码编译阶段就可以检查出数据类型的错误,同时扩展了 JavaScript 的语法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下运行。
为了保证兼容性, TypeScript 会在编译阶段通过编译器编译成纯 JavaScript 来运⾏,是为⼤型应⽤之开发⽽设计的语⾔。
特性:
TypeScript 的特性主要有如下:
- 静态类型系统:TypeScript 允许开发者在编写代码时定义变量、函数和参数的类型。这样可以在编译阶段捕获许多常见错误,提高代码的可靠性和可维护性。
- 类型推断:TypeScript 具有类型推断的能力,这意味着它可以根据代码上下文自动推断变量和表达式的类型,从而减少了显式类型注解的需要。
- 接口:TypeScript 支持接口,允许开发者定义自定义的数据结构,这些结构可以在代码中用作类型注解,以确保数据的一致性和正确性。
- 类:TypeScript 支持面向对象编程,可以使用类来创建对象,实现封装、继承和多态等概念。
- 泛型:TypeScript 提供泛型支持,使得代码可以更灵活地处理不同类型的数据,同时保持类型安全。
- 枚举:TypeScript 允许开发者定义枚举类型,用于表示一组具名常量,这有助于提高代码的可读性和可维护性。
- 装饰器:TypeScript 支持装饰器,可以用来添加元数据和修饰类、方法、属性等,从而实现一些特定的行为,例如实现依赖注入、日志记录等。
TS与JS区别:
TypeScript(简称 TS)和 JavaScript(简称 JS)之间存在几个关键区别:错误检查
、编译过程
、是否具有枚举/泛型等扩展功能、静态类型系统
等等。
- 静态类型系统:
-
- TypeScript 具有静态类型系统,允许开发者在编码时为变量、函数参数等明确指定类型。
- JavaScript 是一种动态类型语言,类型是在运行时确定的,不需要在代码中显式声明类型。
- 编译过程:
-
- TypeScript 需要先将 TypeScript 代码编译成 JavaScript 代码,然后才能在浏览器或其他 JavaScript 运行环境中执行。
- JavaScript 是一种解释性语言,直接由浏览器或其他 JavaScript 运行环境解释执行。
- 可选的类型注解:
-
- TypeScript 中的类型注解是可选的,开发者可以选择是否为变量、函数等添加类型信息。
- JavaScript 中没有类型注解的概念,一切都是动态的。
- 类和接口:
-
- TypeScript 支持类和接口的概念,可以使用面向对象的方式进行编程。
- JavaScript ES6 之后也引入了类的概念,但相较于 TypeScript,其功能较为简单,不支持接口。
- 扩展的功能:
-
- TypeScript 提供了许多 JavaScript 不具备的功能,如枚举、泛型等。
- JavaScript 是一种相对较简单的语言,没有这些高级功能。
- 错误检查:
-
- TypeScript 在编译阶段会进行类型检查和其他静态分析,能够捕获一些常见的错误。
- JavaScript 的错误通常是在运行时才能被检测到。
总的来说,TypeScript 是 JavaScript 的一个超集,它扩展了 JavaScript 的功能,提供了静态类型系统和其他一些高级特性,使得开发者能够编写更加安全、可维护和可扩展的代码。
typescript 的数据类型有哪些?
typescript 和 javascript ⼏乎⼀样,拥有相同的数据类型,另外在 javascript 基础上提供了更加实⽤的类型供开发使⽤。手写讲一下八个在JS中就见过的数据类型:
- 布尔类型: boolean
- 数值类型: number
- 字符串类型: string
- 数组: Array或type[]
- 对象类型: object
- Symbol类型: symbol,表示独一无二的值。ts2.7版本新增了unique symbol这种类型,它只用于常量的定义和属性名,定义时必须用const,不能用let.
- null和undefined: null 和 undefined, 这个比较特殊, 它们自身即是类型
以上这些类型是基础, 我们后面的高级类型很多都是它们的组合或者变形。
除此之外,ts还引入了其它新类型,分别是元组、枚举、any、void、never、unknow。
元组
元祖可以理解为数组的扩展,它用于表示已知元素数量和类型的数组。可以确定数组每一个位置上元素的类型。
let tuple: [string, number, boolean];
tuple = ["a", 2, false];
tuple = [2, "a", false]; // error 不能将类型“number”分配给类型“string”。 不能将类型“string”分配给类型“number”。
注意,超出规定个数的元素称为越界元素,2.6版本之前,越界元素只要是元祖内定义的类型中的一种就可以。2.6之后,要求元组赋值必须类型和个数都对应。
枚举
枚举可以为一些难以理解的常量赋予一组具有意义的名字,比如定义一组角色,每个角色用一个数字代表,就可以使用枚举类型来定义。
enum Roles {
SUPER_ADMIN,
ADMIN,
USER
}
Roles 里面有三个值, TypeScript 默认会为它们每个值分配编号, 默认从 0 开始, 依次排列
any类型
一个变量在不确定它是什么类型时,可以用any类型。any类型的变量可以用于赋予任何类型的值。如果类型是未知的, 更安全的做法是使用unknown类型
void类型
void 和 any 相反, any 是表示任意类型, void 表示什么类型都不是, 例如在定义一个函数, 但是函数没有返回值时可以用void。
void 类型的变量只能赋值为 undefined
和 null
, 其他类型不能赋值给 void 类型的变量。
never类型
never类型表示永不存在的值的类型,例如一个函数如果总会抛出异常,那么它的返回值就是never类型。表明它的返回值是永不存在的
const errorFunc = (message: string): never => {
throw new Error(message);
}
never 类型是任何类型的子类型, 所以never可以赋值给任何类型; 而没有类型是 never 的子类型, 所以除了它自身没有任何类型可以赋值给 never 类型, any 类型也不能赋值给 never 类型。
unknow
unknow表示未知类型,与any类似,但是unknow相对于any来说更安全。一个值赋值为any类型,那可以对它进行任何属性方法的访问(失去ts类型检测的意义)。但是指定一个值是unknow类型时,如果不通过基于控制流的类型断言来缩小范围,是不能对该值进行操作的。
交叉类型与联合类型
交叉类型:
交叉类型使用 &
定义,被&链接的多个类型构成一个交叉类型。这个类型表示同时具备这几个连接起来的类型的特点。
interface Dog {
bark(): void;
}
interface Bird {
fly(): void;
}
type DogAndBird = Dog & Bird;
交叉类型 DogAndBird,表示同时具有狗和鸟的行为。
联合类型:
联合类型由两个或多个其他类型组成,表示可以是这些类型中的任何一个值。使用 |
符号定义,元素类型不
唯一时, 即使用联合类型。
下列代码指定参数既可以是字符串类型也可以是数值类型, typeof content === “string”
是类型保护。
const getLength = (content: string | number): number => {
if (typeof content === "string") return content.length;
else return content.toString().length;
};
console.log(getLength("abc")); // 3
console.log(getLength(123)); // 3
枚举类型
枚举可以为一组难以理解的常量赋予一组直观的名字,枚举是可以细分为数字枚举、字符串枚举以及异构枚举的。
需要注意的是,ts的枚举本质上其实是一个对象,TypeScript 会把我们定义的枚举值的字段名分别作为对象的属性名和值, 把枚举值的字段值分别作为对象的值和属性名,
{
200: "Success",
404: "NotFound",
500: "Error",
Error: 500,
NotFound: 404,
Success: 200
}
但是如果定义枚举之前加上const
关键字,编译后的代码不会创建这个对象, 只是会从枚举里拿到相应的值进行替换
对于数字枚举:
数字枚举是指枚举值的元素是number类型,使用枚举值的元素值时, 就像访问对象的属性一样, 你可以使用’.‘操作符和’[]'两种形式访问里面的值
enum Status {// 这里你的TSLint可能会报一个: 枚举声明只能与命名空间或其他枚举声明合并。 这样的错误, 这个不影响编译, 声明合并的问题
我们在后面的小节会讲。
Uploading,
Success,
Failed
}
console.log(Status.Uploading); // 0
console.log(Status["Success"]); // 1
console.log(Status.Failed); // 2
数字枚举在定义值的时候, 可以使用计算值和常量。 但是要注意, 如果某个字段使用了计算值或常量, 那么该字段后面紧接着的字段必须设置初始值, 这里不能使用默认的递增值了
此处存在一个反向映射的概念:也就是可以通过枚举变量的值来获取该枚举变量的名字。需要注意,反向映射只支持数字枚举
enum Status {
Success = 200,
NotFound = 404,
Error = 500
}
console.log(Status["Success"]); // 200
console.log(Status[200]); // 'Success'
console.log(Status[Status["Success"]]); // 'Success
对于字符串枚举:
字符串枚举值要求每个字段的值都必须是字符串字面量, 或者是该枚举值中另一个字符串枚举成员
对于异构枚举:
异构枚举就是枚举值中成员值既有数字类型又有字符串类型,
enum Result {
Faild = 0,
Success = "Success"
}
枚举成员类型
如果枚举值里所有成员的值都是字面量类型的值, 那么这个枚举的每个成员都可以作为类型来使用。
如果枚举中的成员的值满足下列条件(值是字面量),则可以作为类型使用:
- 不带初始值的枚举成员, 例如 enum E { A }
- 值为字符串字面量, 例如 enum E { A = ‘a’ }
- 值为数值字面量, 或者带有 - 符号的数值字面量, 例如 enum E { A = 1 } 、 enum E { A = -1 }
enum Animal {
Dog = 1,
Cat = 2
}
interface Dog {
type: Animal.Dog; // 这里使用Animal.Dog作为类型, 指定接口Dog的必须有一个type字段, 且类型为Animal.Dog
}
联合枚举类型
枚举值符合条件时, 这个枚举值本身就可以看做是一个包含所有成员的联合类型
enum Status {
Off,
On
}
interface Light {
status: Status;
}
enum Animal {
Dog = 1,
Cat = 2
}
const light1: Light = {
status: Animal.Dog // error 不能将类型“Animal.Dog”分配给类型“Status”
};
枚举和常量枚举的区别
枚举会被编译时会编译成一个对象,可以被当作对象使用。
但是如果定义枚举之前加上const关键字,编译后的代码不会创建这个对象, 只是会从枚举里拿到相应的值进行替换,避免额外的性能开销
什么是类型断言
开发过程中,有时我们可能比ts的静态类型系统更了解一个值的类型,这时可以使用类型断言不让ts进行类型检查。类型断言可以把某个值强行指定为特定类型。
类型断言有2种写法,一种是 value , 一种是 value as type ,JSX只能使用as写法
类型断言往往应用在访问联合类型的变量的方法时:
const getLength = (target: string | number): number => {
if (target.length) { // 类型"string | number"上不存在属性"length
return target.length; // // 类型"number"上不存在属性"length
} else {
return target.toString().length;
}
}
ts不确定联合类型的变量是哪个类型时,只能访问联合类型中的公有属性和方法,因此上述代码报错。这是需要使用类型断言,将 tagrget 的类型断言成 string 类型。
说说你对 TypeScript 中⾼级类型的理解?有哪些?
说说你对 接⼝的理解?应⽤场景?
接口(Interfaces)是一种用来定义对象的结构、属性和方法的规范。它们提供了一种方式来强制对象拥有特定的属性或方法。
接口在TypeScript中被广泛用于描述对象的形状,包括对象的属性名和属性值的类型,以及对象的方法。通过接口,可以定义对象应该具备的属性和方法,并在实现时保证对象的结构符合这些定义。
例如:有⼀个函数,这个函数接受⼀个 User 对象,然后返回这个 User 对象的 name 属性,可以通过接⼝描述 user 参数的结构。
interface User {
name: string
age: number
}
const getUserName = (user: User) => user.name
上述传⼊的对象必须拥有 name 和 age 属性,否则 typescript 在编译阶段会报错。
如果想要⼀个属性变成只读属性,在 typescript 只需要使⽤ readonly
声明。
如果要求某一个字段是可选的,属性名后加 ?
接口还可以用于描述函数类型,可以将其用作函数的类型注解。
interface AddFunc {
(num1: number, num2: number): number;
}
const add: AddFunc = (n1, n2) => n1 + n2;
接口支持继承,一个接口可以被多个接口继承, 同样, 一个接口也可以继承多个接口, 多个接口用逗号隔开。
interface Vegetables {
color: string;
}
interface Food {
type: string;
}
interface Tomato extends Food, Vegetables {
radius: number;
}
const tomato: Tomato = {
type: "vegetables",
color: "red",
radius: 1.2
}; // 在定义tomato变量时将继承过来的color和type属性同时声明
什么是混合类型接口
函数是一个对象类型,由于对象可以有属性,所以函数自身也是可以拥有一些属性的。ts3.1版本之后,支持直接给函数添加属性,3.1之前需要借助命名空间。
readonly和const区别
TypeScript 中不可变量的实现方法有两种:
- 使用 ES6 的 const 关键字声明的值类型
- 被 readonly 修饰的属性
readonly是只读修饰符,通常在 interface 、 Class 、 type 以及 array 和 tuple 类型中使用它,也可以用来定义一个函数的参数
区别如下:
const 用于变量, readonly 用于属性
const 在运行时检查, readonly 在编译时检查
const
声明的变量不得改变值,这意味着,const 一旦声明变量,就必须立即初始化,不能留到以后赋值; readonly
修饰的属性能确保自身不能修改属性,但是当你把这个属性交给其它并没有这种保证的使用者(允许出于类型兼容性的原因),他们能改变
那 readonly和 const 在使用时该如何选择呢? 这主要看你这个值的用途, 如果是定义一个常量, 那用 const , 如果这个值是作为对象的属性,那请用 readonly。
什么是多余属性检查?如何绕开
多余属性检查是指传入了接口中所没有定义的属性,例如一个接口Vegetables包含color和type属性,但是为它传值时多传入了一个size
属性,就会进行多余属性检查
interface Vegetables {
color?: string;
type: string;
}
getVegetables({
type: "tomato",
size: "big" // 'size'不在类型'Vegetables'中
});
但有时候不希望ts对数据进行这么严格的检查,绕开属性检查的方式有三种:使用类型断言、添加索引签名、利用类型兼容性。
类型断言
使用类型断言:直接将传入的参数类型断言为对应接口类型,告诉ts不希望对这个类型进行检查。
getVegetables({
type: "tomato",
size: 12,
price: 1.2
} as Vegetables);
添加索引签名:可以通过添加索引签名来告诉 TypeScript 允许对象拥有额外的属性。
索引签名是使用 [key: type]
的语法来定义的,它允许我们在接口中指定一个字符串或数字索引类型,用于表示额外属性的类型
interface Vegetables {
color: string;
type: string;
[prop: string]: any;
}
在Vegetables接口中添加字符串索引签名[prop: string]: any;允许额外的属性,并且类型是any
利用类型兼容性:类型兼容性就是先把对象字面量赋值给变量,然后再将变量传入函数,前者和直接把对象字面量传入函数的检查机制不同。
interface Vegetables {
type: string;
}
const getVegetables = ({ type }: Vegetables) => {
return `A ${type}`;
};
const option = { type: "tomato", size: 12 };
getVegetables(option)
any 类型的作用是什么?
any 类型用于表示任意类型。当我们不知道变量的类型或者变量的类型可能是多种类型之一时,可以使用 any 类型来告诉 TypeScript 编译器不对该变量进行类型检查,从而允许该变量接受任何类型的赋值,并且可以调用任意属性和方法。
使用 any 类型可以在一定程度上绕过 TypeScript 的类型检查,但也意味着失去了 TypeScript 提供的类型安全性和类型检查功能。
ts的interface和type区别
interface 只能定义对象类型,而 type 可以定义任何类型。
interface 可以被合并(merge),如果多个同名接口存在,它们会被自动合并为一个接口,而 type 不行。
interface 支持扩展(extends),可以扩展其他接口;而 type 不支持扩展。
使用场景:
- 如果需要定义一个对象的类型,应该使用 interface。
- 如果需要定义一个联合类型、交叉类型等,或者需要定义一个类型别名,应该使用 type。
- 如果需要对一个已有的类型进行扩展,或者需要合并多个同名接口,应该使用 interface。
const 和 readonly 的区别?
const 用于声明常量,常量的值不能修改。
readonly 用于声明只读属性,表示属性只能在声明时或构造函数中被赋值,之后不能再被修改。通常用于类的成员变量,以确保成员变量只能在初始化时被赋值,并在之后不会被修改。
any、never、unknown、null & undefined 和 void 有什么区别?
any 表示任意类型。
never 表示永远不会出现的值的类型。
unknown 表示未知类型,比 any 更加类型安全。
null 和 undefined 是空值或未定义值。默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量
void 表示没有返回值的函数的返回类型。
any和unknown有什么区别?
any 类型是 TypeScript 中的最不安全的类型,可以表示任意类型的值,对变量进行任意操作。
unknown 类型表示未知类型,要求在使用时进行类型检查或类型断言,更加类型安全,避免了意外的类型错误。通常情况下,应尽量避免使用 any 类型,而是优先使用更安全的 unknown 类型。
讲一下TS中的泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
在定义函数之前,使用<>
符号定义了一个泛型变量 T
T 在这次函数定义中就代表某一种类型,它可以是基础类型,也可以是联合类型等高级类型。定义了泛型变量之后,你在函数中任何需要指定类型的地方使用 T 都代表这一种类型。
const getArray = <T>(value: T, times: number = 5): T[] => {
return new Array(times).fill(value);
};
泛型约束
使用了泛型时,就意味着这个这个类型是任意类型。但在大多数情况下,我们的逻辑是对特定类型处理的。例如当访问一个泛型类型的参数的 length 属性时,会报错"类型“T”上不存在属性“length”",是因为并不是所有类型都有 length 属性。
所以我们在这里应该对 T
有要求,那就是类型为 T 的值应该包含 length 属性。说到这个需求,你应该能想到接口的使用,我们可以使用接口定义一个对象必须有哪些属性:
泛型约束就是使用一个类型和extends对泛型进行约束。
interface ValueWithLength {
length: number;
}
const getLength = <T extends ValueWithLength>(param: T): number => {
return param.length;
};
getLength("abc"); // 3
getLength([1, 2, 3]); // 3
getLength({ length: 3 }); // 3
getLength(123); // error 类型“123”的参数不能赋给类型“ValueWithLength”的参数
泛型变量T受到约束。它必须满足接口ValueWithLength,也就是不管它是什么类型,但必须有一个length属性,且类型为数值类型。例子中后面四次调用getLength方法,传入了不同的值,传入字符串"abc"、数组[1, 2, 3]和一个包含length属性的对象{ length: 3 }都是可以的,但是传入数值123不行,因为它没有length属性。
泛型类
class GenericNumber<T> {
name: T
add: (x: T, y: T) => T
}
let generic = new GenericNumber<number>()
generic.name = 123
泛型数组
- 写法一
function func<T>(params: T[]) {
return params
}
func<string>(['1', '2'])
func<number>([1, 2])
- 写法二
function func1<T>(params: Array<T>) {
return params
}
func1<string>(['1', '2'])
func1<number>([1, 2])
泛型接口
可以用来约束函数
interface Cart<T> {
list: T[]
}
let cart: Cart<number> = { list: [1, 2, 3] }
泛型别名
type Cart2<T> = { list: T[] } | T[]
let c1: Cart2<number> = { list: [1, 2, 3] }
let c2: Cart2<number> = [1, 2, 3]
泛型接口 VS 泛型别名
- 接口创建了一个新的名字,它可以在其他任意地方被调用。而类型别名并不创建新的名字
- 类型别名不能被 extends 和 implements,这时我们应该尽量使用接口代替类型别名
- 当我们需要使用联合类型或者元组类型的时候,类型别名会更合适
默认泛型
function createArray<T = number>(length: number, value: T): T[] {
let arr: T[] = []
for (let i = 0; i < length; i++) {
arr[i] = value
}
return arr
}
let arr = createArray(3, 9)
泛型工具类型
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Pick 等。它们都是使用 keyof 实现。
keyof 操作符可以用来一个对象中的所有 key 值。
interface Person1 {
name: string
age: number
sex?: string
}
type PersonKey = keyof Person1 // 限制 key 值的取值
function getValueByKey(p: Person1, key: PersonKey) {
return p[key]
}
内置的工具类型
// type Partial<T> = {[P in keyof T]?: T[P]}
type PersonSearch = Partial<Person> // 全部变可选
// type Required<T> = { [P in keyof T]-?: T[P] }
type PersonRequired = Required<Person> // 全部变必选
// type ReadOnly<T> = { readonly [P in keyof T]: T[P] }
type PersonReadOnly = Readonly<Person> // 全部变只读
// type Pick<T, K extends keyof T> = {[P in K]: T[P]}
type PersonSub = Pick<Person, 'name'> // 通过从Type中选择属性Keys的集合来构造类型。