目录
一、TypeScript 介绍
1. TypeScript 是什么
2. TypeScript为什么要为JS添加类型支持
3.TypeScript 相比JS的优势
二、TypeScript 初体验
1.安装编译TS的工具包
2.编译并运行TS代码
3. 简化运行TS的步骤
三、TypeScript 常用类型
(一)概述
(二)类型注解
(三)常用基础类型概述
(四)原始类型
(五)联合类型
(六)类型别名
(七)函数类型
(八)对象类型
(九)接口
(十)元组
(十一)类型推论
(十二)类型断言
(十三)字面量类型
(十四)枚举类型
(十五)any类型
(十六)TS中的typeof运算符
TypeScript(简称:TS)是JavaScript的超集
TypeScript = Type(类型) + JavaScript(在JS基础上,为JS增加了类型支持)
TypeScript是微软开发的开源编程语言,可以在任何运行JavaScript的地方运行。
// TypeScript
let age1: number = 18
// JavaScript
let age2 = 18
背景:JS的类型系统存在“先天缺陷”,JS代码中绝大部分错误都是类型错误(UncaughtTypeError)。
问题:增加了找Bug、改Bug的时间,严重影响开发效率。
从编程语言的动静来区分,TypeScript属于静态类型的编程语言;JS属于动态类型的编程语言。
静态类型:编译期做类型检查;
动态类型:执行期做类型检查。
代码编译和代码执行的顺序:编译 => 执行
对于JS来说:需要等到代码真正去执行的时候才能发现错误(晚);
对于TS来说:在代码编译的时候(代码执行前)就可以发现错误(早)。
并且,配合VSCode等开发工具,TS可以提前到在编写代码的同时就发现代码中的错误,减少找Bug、改Bug的时间。
1)更早(写代码的同时)发现错误,减少找Bug、改Bug的时间,提升开发效率;
2)程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验;
3)强大的类型系统提升了代码的可维护性,使得重构代码更加容易。
4)支持最新的ECMAScript语法,优先体验最新的语法,走在前端技术的最前沿。
5)TS类型推断机制,不需要在代码中的每个地方都显示标注类型,在享受优势的同时,尽量降低了成本。
除此之外,Vue3源码使用TS重写,Angular默认支持TS、React与TS完美配合,TypeScript已经成为大中型前端项目的首先编程语言
问题:为什么要安装编译TS的工具包?
回答:Node.js/浏览器只认识JS代码,不认识TS代码。需要先将TS代码转化为JS代码,然后才能运行。
安装命令:npm i -g typescript
typescript包:用来编译TS代码的包,提供了tsc命令,实现了TS->JS的转化。
验证是否安装成功:tsc -v(查看typescript的版本)
1)创建hello.ts 文件(注意:TS文件的后缀名为 .ts)
2)将TS编译为JS:在终端输入命令,tsc hello.ts (此时,在同级目录中会出现一个同名的JS文件)。
3)执行JS代码:在终端中输入命令,node hello.js
(类似CSS预处理器?less?)
说明:所有合法的JS代码都是TS代码,有JS基础只需要学习TS的类型即可。
注意:由TS编译生成的JS文件,代码中就没有类型信息了
实践:
hello.ts
let age1: number = 18
let age2: number = 20
console.log(age1+age2)
tsc hello.ts ==> 生成hello.js
var age1 = 18;
var age2 = 20;
console.log(age1 + age2);
运行hello.js得到结果
问题: 每次修改代码后,都要重复执行两个命令,才能运行TS代码,太繁琐。
简化方式:使用ts-node包,直接在Node.js中执行TS代码。
安装命令:npm i -g ts-node(ts-node包提供了ts-node命令)。
使用方式: ts-node hello.ts
解释:ts-node 命令在内部偷偷的将TS->JS,然后再运行JS代码。
实践:
hello.ts:
let age1: number = 18
let age2: number = 20
console.log(age2-age1)
ts-node 并未生成.js文件,在内部转化并执行。
TS是JS的超集,TS提供了JS的所有功能,并且额外的增加了:类型系统。
TS类型系统的主要优势:可以显示标记出代码中的意外行为,从而降低了发生错误的可能性。
1)类型注解
2)常用基础类型
JS类型Bug:
字符串无toFixed()方法,执行后报错。
let count = 18
// count = '20'
count = '20'
console.log(count.toFixed())
TS类型Bug标记:
直接报错,指出问题。
说明:代码中的:number 就是类型注解。
作用:为变量添加类型约束。
解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错。
可以将TS中常用基础类型细分为两类:JS已有类型;TS新增类型
1.JS已有类型
2.TS新增类型
1. 原始类型:number/string/boolean/null/undefined/symbol
特点:简单。这些类型完全按照JS中类型的名称来书写。
let age: number = 18
let username: string = '康老师'
let isAngry: boolean = true
let isNull: null = null
let isUndefined: undefined = undefined
let sym:symbol = Symbol('22')
2.对象类型:object(包括数组、对象、函数等对象)
特点:对象类型,在TS中更加细化,每个具体的对象都有自己的类型语法。
let nums:number[] = [1,2,3,5]
let strings:Array = ['a','b','c']//不推荐
let bo:boolean[] = [true,false]
需求:数组中既有number类型,又有string类型,数组写法:
// 数组既有number又有string
let arr:(number | string)[] = [1,'a','s',3]
解释:|(竖线)在TS中叫做联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的一种)
注意:这是TS中联合类型的语法,只有一根竖线,不要与JS中的或(||)混淆了。
类型别名(自定义类型):为任意类型起别名。
type CustomArray = (number | string )[]
let arr1: CustomArray = [1,'a',3,'d']
let arr2: CustomArray = ['a',2,'c',4]
解释:
函数的类型实际上指的是:函数参数和返回值的类型。
为函数指定类型的两种方式:
1.单独指定参数、返回值的类型:
// 单独指定
function add(num1: number, num2: number): number {
return num1 + num2
}
console.log(add(1, 2))
const add2 = (n1: number, n2: number): number => {
return n1 + n2
}
console.log(add2(3,4))
运行结果:
2.同时指定参数、返回值的类型:
// 同时指定
const add3: (nu1: number, nu2: number) => number = (n1, n2) => {
return n1 + n2
}
console.log(add3(5,6))
(变量add3 是一个函数类型的变量,该变量的类型为 参数为两个number类型,返回值为number类型的函数 等于 一个箭头函数???)
运行结果:
解释:当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型。
注意:这种形式只适用于函数表达式。
如果函数没有返回值,那么函数返回值为:void类型
const add3: (nu1: number, nu2: number) => void = (n1, n2) => {
console.log(n1+n2)
}
add3(7,8)
可选参数:
使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就用到可选参数了。
比如,数组的slice方法,可以slice()/slice(1)/slice(1,3)。
// 可选参数
function mySlice(start?: number, end?: number): void {
console.log('起始索引:' + start + ',最终索引:'+end)
}
mySlice()
mySlice(1)
mySlice(1,3)
运行:
可选参数:在可传可不传的参数名称后面添加?(问号)
注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数。
JS中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么类型的属性和方法)。
// 对象类型写法
let user1: { uname: string; age: number; sayHi(): void } = {
uname: 'zs',
age: 20,
sayHi() {
console.log('Hi')
}
}
// 对象类型写法
let user: {
uname: string
age: number
sayHi:() => void
add(n1:number,n2:number):number
} = {
uname: 'zs',
age: 20,
sayHi:()=> {
console.log('Hi')
},
add(n1, n2) {
return n1 + n2
}
}
解释:
对象可选属性:
对象的属性或方法,也可以是可选的,此时就用到可选属性了
比如我们在使用axios({...})时,如果发送GET请求,method属性就可以省略。
// 对象可选参数
function myAxios(config: { url: string; method?: string }) {
console.log(config)
}
myAxios({ url: 'http://www.baidu.com' })
myAxios({ url: "ssss" , method:'POST'})
可选属性的语法与函数可选参数的语法一致,都使用?来表示。
1.接口
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。
代码:
// 接口
interface IPerson {
name: string
age: number
sayHi(): void
add:(n1:number,n2:number)=>number
}
let person1:IPerson = {
name: 'zs',
age: 20,
sayHi() {
console.log('Hi')
},
add(n1,n2) {
return n1+n2
}
}
解释:
2.接口(interface)与类型别名(type)的对比:
// 接口与类型别名
interface IPerson {
name: string
age: number
sayHi(): void
add:(n1:number,n2:number)=>number
}
// 类似于声明变量:变量=类型
type IPerson1 = {
name: string
age: number
sayHi(): void
add: (n1: number, n2: number) => number
}
3.接口继承:
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。
eg:
// 接口的继承
interface Point2D {
x: number
y: number
}
interface Point3D extends Point2D{
z:number
}
let e:Point3D = {
x: 3,
y: 4,
z: 5
}
解释:
场景:在地图中,使用经纬度坐标来标记位置信息。
可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型。
使用numbers[]的缺点:
不严谨,因为该类型的数组中可以出现任意多个数字。
更好的方式:元组(Tuple)
元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型。
// numner[] 不严谨
let position: number[] = [39.222, 33.999]
// 元组
let posi:[number,number] = [39.222, 33.999]
解释:
在TS中,某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型。
换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写。
发生类型推论的两种常见场景:
注意:这两种情况下,类型注解可以省略不写。
如果声明变量但没有立即初始化值,此时,还必须手动添加类型注解。
推荐:能省略类型注解的地方就省略(充分利用TS类型推论的能力,提升开发效率)。
技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用VSCode的提示来查看类型。
有时候你会比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。
使用类型断言:
// 一般语法:as关键字
const aLink = document.getElementById('link') as HTMLAnchorElement
// <>语法--了解
const al = document.getElementById('link')
console.log(aLink.href)
解释 :
技巧:在浏览器控制台,通过console.dir()打印DOM元素,在属性列表的最后面,即可看到该元素的类型。
解释:
注意:此处的'Hello TS',就是一个字面量类型。也就是说某个特定的字符串也可以作为TS这的类型。
除字符串外,任意的JS字面量(eg:对象、数字等)都可以作为类型使用。
使用模式:字面量类型配合联合类型一起使用。
使用场景:用来表示一组明确的可选值列表。
eg:
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
console.log(direction)
}
解释:参数direction的值只能是up/down/left/right
优势:相比于string类型,使用字面量类型更加精确、严谨。
1. 枚举
枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。
枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。
enum Direaction { Up, Down, Left, Right }
function changeDirection(direction: Direaction) {
console.log(direction)
}
// 访问枚举成员:枚举.成员
changeDirection(Direaction.Up)
解释:
注意:形参direction的类型为枚举Direction,那么,实参的值就应该是枚举Direction成员的任意一个。
解释:类似于JS中的对象,直接通过点(.)语法访问枚举的成员。
2. 枚举成员的值和数字枚举
问题:我们把枚举成员作为了函数的实参,它的值是什么呢?
解释:枚举成员Up的值为0。
注意:枚举成员是有值的,默认为:从0开始自增的数值。
我们把,枚举成员的值为数字的枚举,称为:数字枚举。
当然,也可以给枚举中的成员初始化值。
3. 字符串枚举
字符串枚举:枚举成员的值是字符串。
enum Direaction { Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT'}
Direaction.Down // DOWN
Direaction.Left // LEFT
注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
4. 枚举的特点及原理
枚举是TS为数不多的非JavaScript类型级扩展(不仅仅是类型)的特性之一。
因为:其他类型仅仅被当作类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)。
也就是说,其他的类型会在编译为JS代码时自动移除。但是,枚举类型会被编译为JS代码。
说明:枚举与前面讲到的字面量+联合类型组合的功能类似,都用来表示一组明确的可选值列表。
一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效。
对比:
原则:不推荐使用any
这会让TypeScript变为“AnyScript”(失去TS类型保护的优势)。
因为当值的类型为any时,可以对该值进行任意操作,并且不会有代码提示。
解释:以上操作都不会有任何类型错误提示,即使可能存在错误。
尽可能的避免使用any类型,除非临时使用any来避免书写很长、很复杂的类型。
其他隐式具有any类型的情况:
注意:因为不推荐使用any,所以这两种情况下都应该提供类型。
众所周知,JS中提供了typeof操作符,用来在JS中获取数据的类型。
实际上,TS也提供了typeof操作符:可以在类型上下文中引用变量或属性的类型(类型查询)。
使用场景:根据已有变量的值获取该值的类型,来简化类型书写。
let p = { x: 1, y: 2 }
// function formatPoint(point: { x: number, y: number }) { }
function formatPoint(point: typeof p) { }
formatPoint(p)
解释: