挑战坚持学习1024天——TypeScript
Day158-Day202【2022年1月18日-2023年3月2日】
开年过的很快,把typescript重难点着重学了一遍,接下来的时间全力准备面试。加油打工人!
学习重点 TypeScript
一. TypeScript的意义
TypeScript 是一种由微软开发的编程语言,它是 JavaScript 的超集,可以让 JavaScript 的开发更加高效、可维护、可扩展。TypeScript 最大的特点是增加了静态类型检查,这使得在开发大型项目时更加安全可靠。
在前端中使用 TypeScript 的意义主要体现在以下几个方面:
- 静态类型检查:TypeScript 可以在开发过程中发现类型相关的错误,并在编译期间进行提示,这可以减少因为类型错误而导致的运行时错误,提高代码质量和稳定性。
- IDE 支持:TypeScript 可以提供更好的 IDE 支持,如代码自动补全、类型检查和错误提示等。这可以提高开发效率和代码质量。
- 更好的可维护性和可扩展性:TypeScript 可以使用面向对象编程中的概念,如类、接口、泛型等。这可以使代码更加易于维护和扩展。
- 更好的协作性:在多人协作开发时,TypeScript 的类型检查可以帮助团队成员更好地理解代码,降低团队沟通的成本。
总之,TypeScript 在前端中的意义主要是提高代码质量和稳定性,提高开发效率和协作效率,使得代码更易于维护和扩展。
二. JavaScript有什么缺点?TypeScript弥补了JavaScript的什么缺点?
JS缺点
- 比如ES5以及之前的使用的var关键字关于作用域的问题
- 比如最初JavaScript设计的数组类型并不是连续的内存空间
- 比如直到今天JavaScript也没有加入类型检测这一机制
编程开发中我们有一个共识:错误出现的越早越好
如何在 代码编译期间 发现代码的错误
JavaScript可以做到吗?不可以
TypeScript的出现弥补了JavaScript类型约束上的缺陷
TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码
我们可以将TypeScript理解成加强版的JavaScript
JavaScript所拥有的特性,TypeScript全部都是支持的,并且它紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是支持的
并且TypeScript最终会被编译成JavaScript代码,所以你并不需要担心它的兼容性问题
三. TypeScript有什么特点?哪些框架或者项目在使用TypeScript?
始于JavaScript,归于JavaScript TypeScript可以编译出纯净、 简洁的JavaScript代码
Angular源码
Vue3源码
VSCode也是使用TypeScript来完成的
ant-design的UI库,也大量使用TypeScript来编写
包括小程序开发,也是支持TypeScript的
四. TypeScript的运行环境如何搭建?如何运行TypeScript代码?
-
需要在电脑上安装TypeScript
# 安装命令 npm install typescript -g # 查看版本 tsc --version -
两种方式来运行TypeScript
- 通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上
- 通过ts-node库,为TypeScript的运行提供执行环境
# 方式二:安装ts-node npm install ts-node -g # 另外ts-node需要依赖 tslib 和 @types/node 两个包 npm install tslib @types/node -g # 可以直接通过 ts-node 来运行TypeScript的代码 ts-node math.ts
五. 如何定义TypeScript的变量,定义变量支持哪些JavaScript类型?
# 数字 布尔 字符串类型
let num: number = 123
let flag: boolean = true
const name = 'lgl'
#数组类型
let names: string[] = ["aaa", "bbb", "ccc"]
let nums: Array<number> = [111, 222, 333];
#对象类型
const info: object = {
name: "xdh",
age: 18,
};
# null undefined类型
const n1: null = null;
const n2: undefined = undefined;
# Symbol类型
const title1 = Symbol("title")
const title2 = Symbol('title')
const info = {
[title1]: "程序员",
[title2]: "老师"
}
六. 常见的TypeScript特有类型有哪些?这些类型有什么作用?
- any类型可以设置任意类型
- unknown
- void
- never
- tuple元组类型
七. 什么是联合类型和交叉类型,它们有什么区别?(面试题)
联合类型
- 联合类型是由两个或者多个其他类型组成的类型,
- 交叉类型使用 | 符号, 比如: number |string, 表示可以是这些类型中的任何一个值
- 联合类型中的每一个类型被称之为联合成员
交叉类型
- 交叉类似表示需要满足多个类型的条件
- 交叉类型使用 & 符号, 比如: number & string, 表达的含义是number和string要同时满足
联合类型和交叉类型区别
- 联合类型表示可以是这些类型中的任何一个值
- 交叉类似表示需要满足多个类型的条件
八. 类型别名(Type)和接口类型(Interface)有什么关系和区别?(面试题)
类型别名(Type)
- 可以给类型起一个别名
- Type类型不能出现重复定义
- 接口类型定义的类型也可以使用Type定义
接口类型(Interface)
- 接口类型可以给对象 / 函数等定义类型
- 接口是可以实现多个接口
- 接口也可以被类实现
区别
- 类型别名和接口非常相似,在定义对象类型时,大部分时候,你可以任意选择使用
- 接口的几乎所有特性都可以在 type 中使用, 如果是定义非对象类型,通常推荐使用type
- Type类型不能出现重复定义, 而interface 可以重复的对某个接口来定义属性和方法;
九. 什么是TypeScript的类型缩小?有什么作用?
什么是类型缩小呢?
- 类型缩小的英文是 Type Narrowing(也有人翻译成类型收窄);
- 我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径;
- 在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小( Narrowing );
- 而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards);
常见的类型保护有如下几种:
- typeof
- 平等缩小(比如===、!==)
- instanceof
- in
- 等等...
十. 函数有自己的类型吗?如何定义?什么是函数调用签名?什么是函数构造签名?
定义函数类型
- 可以编写函数类型的表达式(Function Type Expressions),来表示函数类型
- (num1: number, num2: number) => void,代表的就是一个函数类型, 这个函数是没有返回值的,所以是void;
函数调用签名
- 函数类型表达式并不能支持声明属性, 如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个调用签名(call signature)
interface IBar {
name: string
age: number
// 函数可以调用: 函数调用签名
(num1: number): number
}
const bar: IBar = (num1: number): number => {
return 123
}
函数构造签名
- 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors)
- 写一个构造签名(Construct Signatures ),方法是在调用签名前面加一个 new 关键词
class Person {
}
interface ICTORPerson {
new (): Person
}
function factory(fn: ICTORPerson) {
const f = new fn()
return f
}
factory(Person)
十一. 什么是函数的重载,函数的重载有什么作用?
函数的重载和作用
- 函数的重载 就是函数名称相同,函数的参数个数和参数的类型不同
- 在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;
- 一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
- 这样就可以根据我们传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名
函数的重载这里有两种实现方案:
- 方案一:使用联合类型来实现;
- 方案二:实现函数重载来实现;
十二. TypeScript中的函数如何绑定this?有哪些this相关的工具?
函数绑定this
- 函数的第一个参数我们可以根据该函数之后被调用的情况,用于声明this的类型(名词必须叫this)
- 在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除
function foo(this: { name: string }, info: {name: string}) {
console.log(this, info)
}
foo.call({ name: "why" }, { name: "kobe" })
this相关的工具:
-
ThisParameterType:
- 用于提取一个函数类型Type的this (opens new window)参数类型;
- 如果这个函数类型没有this参数返回unknown;
-
OmitThisParameter:
- 用于移除一个函数类型Type的this参数类型, 并且返回当前的函数类型
-
ThisType
- 这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型
十三. TypeScript中类的定义有什么特点?什么是属性修饰符?
类的定义有什么特点:
- 类的定义我们通常会使用class关键字:
- 在面向对象的世界里,任何事物都可以使用类的结构来描述;
- 类中包含特有的属性和方法;
- 类可以有自己的构造函数constructor,当我们通过new关键字创建一个实例时,构造函数会被调用
- 类中可以有自己的函数,定义的函数称之为方法;
类的属性和方法支持三种修饰符: public、private、protected
- public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;
- private 修饰的是仅在同一类中可见、私有的属性或方法;
- protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;
十四. 什么是TypeScript的抽象类?抽象类有什么作用?
抽象类有如下的特点:
- 抽象类是使用abstract声明的类;
- 抽象类是不能被实例的话(也就是不能通过new创建)
- 抽象方法必须被子类实现,否则该类必须是一个抽象类;
- 抽象方法,必须存在于抽象类中;
抽象类有什么作用:
- 抽象类是用来捕捉子类的通用特性的,是被用来创建继承层级里子类的模板。
- 现实中有些父类中的方法确实没有必要写,因为各个子类中的这个方法肯定会有不同;
- 而写成抽象类,这样看代码时,就知道这是抽象方法,而知道这个方法是在子类中实现的,有提示作用。
十五. 什么是类类型?类类型具有什么样的特点?
类的作用和类类型:
- 类可以创建类对应的实例对象
- 类本身可以作为这个实例的类型, 即作为类类型用
- 类也可以当中有一个构造签名的函数
特点
- 可以作为类使用, 也是可以作为类类型使用
- 类也可以当中有一个构造签名的函数
十六. 接口和抽象类有什么区别(面试题,特别是Java程序员面试的时候)
接口和抽象类的区别:
(1)抽象类可以有构造方法,接口中不能有构造方法。
(2)抽象类中可以有普通成员变量,接口中没有普通成员变量
(3)抽象类中可以包含静态方法,接口中不能包含静态方法
(4) 一个类可以实现多个接口,但只能继承一个抽象类。
(5)接口可以被多重实现,抽象类只能被单一继承
(6)如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
接口和抽象类的相同点:
(1) 都可以被继承
(2) 都不能被实例化
(3) 都可以包含方法声明
(4) 派生类必须实现未实现的方法
十七. 什么严格的字面量赋值检测,在行为上有什么特点?
严格的字面量赋值检测
- 对于对象的字面量赋值, 每个对象字面量最初都被认为是“新鲜的(fresh)”
- 对于新鲜的字面量, 会进行严格的类型检测. 必须完全满足类型的要求(不能有多余的属性)
- 当类型断言或对象字面量的类型扩大时,新鲜度会消失。
十八. 什么是泛型?泛型有什么作用,如何使用?
什么是泛型
- 软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性
- 比如 我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作
- 对于函数参数的类型进行参数化, 就是泛型的体现
- 泛型除了在函数上使用,也通常会在类 或 接口等地方使用
function foo<T, E>(arg1: T, arg2: E) {
}
foo(10, 20)
foo(10, "abc")
foo<string, { name: string }>("abc", { name: "why" })
export {}
十九. 什么是泛型约束和泛型条件?在开发中如何被使用?
泛型约束
- 有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中
- 这时我们就会这些不同的类型进行一个约束, 这些类型必须具备某些相同的共性
使用
- extends关键字来约束
// 传入的key类型, obj当中key的其中之一
interface IKun {
name: string
age: number
}
type IKunKeys = keyof IKun // "name"|"age"
function getObjectProperty<O, K extends keyof O>(obj: O, key: K){
return obj[key]
}
const info = {
name: "why",
age: 18,
height: 1.88
}
const name = getObjectProperty(info, "name")
export {}
二十. TypeScript中映射类型有什么特点?如何使用?
映射类型
- 一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型
- 比如: 大部分内置的工具都是通过映射类型来实现的, 还有大多数类型体操的题目也是通过映射类型完成的
映射类型建立在索引签名的语法上使用
- 映射类型,就是使用了 PropertyKeys 联合类型的泛型;
- 其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型
// TypeScript提供了映射类型: 函数
// 映射类型不能使用interface定义
// Type = IPerson
// keyof = "name" | "age"
type MapPerson<Type> = {
// 索引类型以此进行使用
[aaa in keyof Type]: Type[aaa]
}
interface IPerson {
name: string
age: number
}
type NewPerson = MapPerson<IPerson>
export {}
二十一. TypeScript条件类型如何使用?解释一下什么是infer和distributive?(了解)
条件类型使用
- 条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression )
- 比如: SomeType extends OtherType ? TrueType : FalseType;
type IDType = number | string
// 判断number是否是extends IDType
// const res = 2 > 3? true: false
type ResType = boolean extends IDType? true: false
function sum<T extends number | string>(num1: T, num2: T): T extends number? number:string
function sum(num1, num2) {
return num1 + num2
}
const res = sum(20, 30)
const res2 = sum("abc", "cba")
export {}
inter关键字
- 它用来在条件类型中推断
- 可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果
type CalcFnType = (num1: number, num2: string) => number
function foo() {
return "abc"
}
// 总结类型体操题目: MyReturnType
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R? R: never
type MyParameterType<T extends (...args: any[]) => any> = T extends (...args: infer A) => any? A: never
// 获取一个函数的返回值类型: 内置工具
type CalcReturnType = MyReturnType<CalcFnType>
type FooReturnType = MyReturnType<typeof foo>
type CalcParameterType = MyParameterType<CalcFnType>
export {}
distributive关键字
- 用作分发条件类型
- 当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成分发的(distributive)
type toArray<T> = T extends any? T[]: never
type NumArray = toArray<number>
// number[]|string[] 而不是 (number|string)[]
type NumAndStrArray = toArray<number|string>
二十二. 列举一下常见的TypeScript内置类型工具?分别说出它们的作用以及实现。
内置类型工具
- Partial<Type> : 用于构造一个Type下面的所有属性都设置为可选的类型
- Required<Type> : 用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反。
- Readonly<Type> : 用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值。
- Record<Keys,Type> : 用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型。
- Pick<Type,Keys>: 用于构造一个类型,它是从Type类型里面挑了一些属性Keys
- Omit<Type,Keys> : 用于构造一个类型,它是从Type类型里面过滤了一些属性Keys
- Exclude<UnionType,ExcludedMembers> : 用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型
- Extract<Type,Union> : 用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型。
- NonNullable<Type>: 用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型。
- InstanceType<Type> : 用于构造一个由所有Type的构造函数的实例类型组成的类型。
二十三. TypeScript模块化和JavaScript模块化有什么区别?什么是命名空间?
TypeScript模块化和JavaScript模块化的区别
- JavaScript有一个很长的处理模块化代码的历史, 比如:以前会将一个模块封装到一个立即执行函数中
- 随着时间流逝,社区和 JavaScript规范已经使用为名为 ESModule的格式, 这也就是我们所知的import/export语法
- JavaScript 规范声明任何没有 export 的 JavaScript文件都应该被认为是一个脚本,而非一个模块
- 在TypeScript中最主要使用的模块化方案就是ESModule, 在TS中如果想要让一个JS文件编程模块化可以添加一个export{} 语法
命名空间
- 命名空间在TypeScript早期时,称之为内部模块
- 目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题;
- 虽然命名空间没有被废弃,但是由于 ES 模块已经拥有了命名空间的大部分特性,因此更推荐使用 ES 模块
二十四. 什么是TypeScript声明文件,声明文件如何分类?如何引入和起作用?
TypeScript声明文件
- 我们之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方;
- 还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare),称之为类型声明(Type Declaration)或者类型定义(Type Definition)文件。
声明文件如何分类
- 内置类型声明: 是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件,不需导入
- 外部定义类型声明: 是我们使用一些库(比如第三方库)时,需要的一些类型声明, 需要额外安装
- 自己定义类型声明: 在自己的代码中声明一些类型,方便在其他地方直接进行使用;
二十五. 如何编写自己的声明文件?可以声明哪些类型?(掌握)
编写自己的声明文件
- 自己新建一个 .d.ts 文件,然后在该文件中根据声明模块的语法编写类型声明
- 比如:可以声明变量 函数 类 模块 文件 等等
declare module "lodash" {
export function join(...args: any[]): any
}
// 为自己的 变量/函数/类 定义类型声明
declare const whyName: string
declare const whyAge: number
declare const whyHeight: number
declare function foo(bar: string): string
declare class Person {
constructor(public name: string, public age: number)
}
// 作为一个第三方库为其他开发者提供类型声明文件 .d.ts => axios.d.ts
// 声明文件模块
declare module "*.png"
declare module "*.jpg"
declare module "*.jpeg"
declare module "*.svg"
declare module "*.vue"
// 声明成模块(不合适)
// 声明命名空间
declare namespace $ {
export function ajax(settings: any): any
}
二十六. tsconfig.json文件的作用是什么?有哪些比较常见的配置?
tsconfig.json文件有两个作用:
-
作用一(主要的作用):让TypeScript Compiler在编译的时候,知道如何去编译TypeScript代码和进行类型检测;
- 比如是否允许不明确的this选项,是否允许隐式的any类型;
- 将TypeScript代码编译成什么版本的JavaScript代码;
-
作用二:让编辑器(比如VSCode)可以按照正确的方式识别TypeScript代码;
- 对于哪些语法进行提示、类型错误检测等等;
比较常见的配置:
{
"compilerOptions": {
"target": "ES2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"skipLibCheck": true
},
"files": [],
"include": ["./src/**/*.ts", "./types/*.d.ts"]
}
参考资料
- Coderwhy学习资料
- TypeScript官网
- 其他参考资料 结语
志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。我们一起努力鸭! ——>点击这里