TypeScript是JavaScript的一个超集,任何合法的JavaScript代码都能在TypeScript中运行,它给原生的JavaScript带来了强类型的定义,需要注意的是TypeScript无法在浏览器中运行。
TypeScript可以规范我们的代码,让我们能够在代码的编译阶段就能及时发现错误,它只是在原生js的基础上加上了一层类型定义
使用TypeScript的好处
- 自动进行类型检查
- 避免出现低级错误
- 节约重复的劳动,解放劳动力
- 帮助我们写出质量更高的代码
1、TypeScript基础
1.1 基础类型
boolean、string、number、array、tuple、enum、null、undefined、object、void、never、any、unknown
当我们为变量指定了类型以后,只允许给变量赋予对类型的值,不然就编译报错
1.1.1 Array
TypeScript中声明array的写法
let list1:number[] = [1,2,3]
let list2: Array<number> = [1,2,3]
let list3 =[1,2,3]
这三个数组都只允许写入number类型,当我们声明变量切未指定类型的时候,数组的类型会自动根据你初始化的类型来定义数组类型
也可以给变量指定多个类型
let list1:(number | string) = [1,2,'dean']
let list2:Array<number | string> = [1,2,'dean']
let list3 = [1,2,'dean']
1.1.2 Tupple 元组类型
即元组类型,元组是一个固定长度,固定类型的数组,声明一个元组的时候一定指明它的类型
改变长度,改变类型都会让程序报错
但是这个类型存在一个bug,当我们使用push方法的时候程序不会报错 且能正常运行
let personal:[number,string] = [1,'dean']
personal.push(3)
console.log(personal) //正常输出[ 1, 'dean', 3 ]
1.1.3 any
any类型其实就是原生js声明的数据类型,即该变量没有指定类型
1.1.4 unknown 类型
unknown 类型 和 any类型有点相似,都是没有定义变量的类型,unknown跟any的最大的不同是,unknown不保证类型,但是能保证类型的安全
//any类型
let val1:any = 666
val1 = true
val1 = 'asfsdf'
val1 = {}
val1()
val1.toUpperCase()
以上的代码,在编译时并不会报错,但是在运行时会报错
//unknown类型
let val1:unknown= 666
val1 = true
val1 = 'asfsdf'
val1 = {}
val1()
val1.toUpperCase()
而把类型定义成unknown的时候 在编译时就会报错,我们在使用unknown变量的时候需要做一定程度的判断或者类型转换,只有了确定了变量类型以后才能正常使用功能对应的函数。
1.1.5 enum 枚举类型
enum Color {
red,
grenn,
blue
}
let color = Color.blue //2
我们也可以指定数据
enum Color {
red='#FF0000',
grenn='#00FF00',
blue='#0000FF'
}
let color = Color.blue // #0000FF
1.1.6 void
一个函数在没有任何返回的情况下,这个函数就是一个void类型;
void和undefined都表示没有,但是undefined指的是变量没有赋值,没有初始化,而void他所说的是变量本身就不存在
function printRes():void {
consol.log('dean')
}
console.log("hello",printRes()) //hello undefined
由于函数printRes是void类型,所以表明他本身就不存在,所以无法打印出函数里面的内容
1.1.7 never
一个函数永远执行不完,就是never类型
function whileLoop(){
while(true){
console.log('lalala')
}
}
//这里的whileLoop函数就是一个never类型
1.2 TypeScript的高级类型
union 联合类型、Nullable可空类型、Literal字面量类型
1.2.1 union 联合类型
联合类型指的是一个变量可以同时支持2个或者2个以上的不同类型,不同类型之间用|来分隔
let un1:string | number | string[]
1.2.2 Literal 字面量类型
字面量类型即明确了取值的类型
let li1: 0 | 1 | 3
let li2: 1 | false | 'dean' | [1,2,3]
当给字面量类型的变量赋值为预定义之外的值时就会报错
1.3 类型适配(类型断言) Type Assertions
声明一个变量时,没有给他指定具体的类型或没有初始化赋值,那他的类型默认就是any,后面再给他赋值,不会转变它的类型,这时候需要用到类型适配
let message //此时变量的类型是any
message ="abc" //虽然给他赋值了 但是此时变量的类型还是没有变化
//方式一
let msg1 = (<string> message)
//方式二
let msg2 = (message as string)
两种方式都可以转换变量的类型
1.4 函数类型
跟es6相比,typescript可以为传入函数的参数指定类型
如果typescript定义了两个参数,那么必须填写参数中所有的参数,而且类型必须匹配
let log2 = (message:string,code:number) =>{
console.log(message,code)
}
log2("hi",200)
若是想省略某一些参数,可以使用**?**来表示某些参数的可选性
let log3 = (message:string,code?:number) =>{
console.log(message,code)
}
log3('hi')//hi undefined
也可以这种参数的默认值
let log2 = (message:string,code:number=1) =>{
console.log(message,code)
}
log2('hi') //hi 1
需要注意的是不管是可选参数还是默认参数都需要参数列表的末尾,而且需要从后到前的顺序排列
只设置第一个参数为可选值,此时代码报错
2 、TypeScript面向对象
2.1 Object对象类型
object的使用方法与原生的类似,但是不同的点是,在TypeScript当我们调用没有定义的内部变量如下的sex,这时候代码就会报错
object的内部变量都有对应的固定的类型
所以与JavaScript不同的是,TypeScript对对象类型的定义不是key to value(键值对),而是key to type (键类型对)
我们也可以选择显式的手动定义对象的类型
const person:{
firstName:string,
lastName:string,
age:number
} = {
firstName:'dean',
lastName:'郭',
age:18
}
2.2 Interface 接口
Interface 可以给参数的对象加以限制
这时所有非法输入都会报错,错误的代码也不会再执行
2.3 class 类
为了让Interface 接口实现高聚合的作用,可以使用class类的概念来实现,有接触过java的应该比较好理解这个类
interface IPoint{
x:number;
y:number;
drawPoint:() =>void;//由于此函数没有返回值所以用void
getDistances:(p:IPoint) =>number
}
class Point implements IPoint{
x:number;
y:number;
drawPoint = () =>{
console.log("x:",this.x,"y:",this.y)
}
getDistances=(p: IPoint) => {
return Math.pow(p.x-this.x,2)+Math.pow(p.y-this.y,2)
}
}
const point =new Point() //创建实例使用new关键词
point.x = 2;
point.y = 3;
point.drawPoint() // x:2 y:3
这里的类是Point,这边的point是对象,对象就是这个类的实例,故对象也被称为实例instance。这里可以举个例子,狗是一个class,大黄是一个对象,大黄出生的这个过程就是实例化
除了直接给坐标点赋值的这个方法,我们还可以使用构造函数constructor
class Point implements IPoint{
x:number;
y:number;
constructor(x:number,y:number){
this.x = x;
this.y = y;
}
drawPoint = () =>{
console.log("x:",this.x,"y:",this.y)
}
getDistances=(p: IPoint) => {
return Math.pow(p.x-this.x,2)+Math.pow(p.y-this.y,2)
}
}
const point =new Point(2,3) //创建实例使用new关键词
ponint.drawPoint() // x:2 y:3
运用了构造函数的话,我们可以在初始化一个Point的时候就可以同时把坐标参数填充进去了
如果在初始化的时候还不想填入参数的话,可以在构造函数中加一个?,即把参数改成可选项,这时候创建新对象就不需要填入参数了,需要注意的是一个类就只有一个constructor
2.4 Access Modifier 访问修饰符
public 公有 (默认情况下所有的成员变量都是public)
private 私有
可以通过使用访问修饰符,在声明构造函数的同时随便完成成员变量的声明以及初始化。
class Point implements IPoint{
//x:number;
//y:number;
constructor(public x:number,public y:number){
//this.x = x;
//this.y = y;
}
drawPoint = () =>{
console.log("x:",this.x,"y:",this.y)
}
getDistances=(p: IPoint) => {
return Math.pow(p.x-this.x,2)+Math.pow(p.y-this.y,2)
}
}
const point =new Point(2,3) //创建实例使用new关键词
point.drawPoint() // x:2 y:3
这边使用public(公有属性)作为关键词
但是设置变量为公有属性会产生一个问题,即便是已经初始化过后的Point对象,我们还是可以对它的x值和y值进行重新赋值,如
const point =new Point(2,3) //创建实例使用new关键词
point.x = 20
point.y = 30
ponint.drawPoint() // x:20 y:30
这种在对象外部直接访问内部成员变量,甚至重新赋值的操作在实际工作中是不安全的,所以我们希望外界避免直接操作Class内部属性,这时候就需要private。
当我们用private来修饰x和y的时候,我们就无法直接访问内部成员变量了
需要注意的是,接口中定义的变量也好,方法也好都必须是公开的,所以当我们使用private来修饰x和y,而接口中定义还是公有属性,代码就会报错,所以需要删除公有属性
在某些场景下,还是需要从外部来访问x和y,为了满足这个条件的同时
保证这两个属性具有高度的私有隔离,这个时候就需要借助getter和setter这两个方法
interface IPoint{
drawPoint:() =>void;//由于此函数没有返回值所以用void
getDistances:(p:IPoint) =>number
getX:() =>number
getY:() =>number
setX:(value) =>void
setY:(value) =>void
}
class Point implements IPoint{
//x:number;
//y:number;
constructor(private x:number,private y:number){
//this.x = x;
//this.y = y;
}
drawPoint = () =>{
console.log("x:",this.x,"y:",this.y)
}
getDistances=(p: IPoint) => {
return Math.pow(p.getX()-this.x,2)+Math.pow(p.getY()-this.y,2)
}
setX = (value:number) =>{
if(value < 0){
throw new Error("x的数值不能小于0")
}
this.x = value
}
setY = (value:number) =>{
if(value < 0){
throw new Error("y的数值不能小于0")
}
this.y = value
}
getX = () =>{
return this.x
}
getY = () =>{
return this.y
}
}
const point =new Point(2,3) //创建实例使用new关键词
point.setX(20)
point.setY(30)
point.getX() //20
point.getY() //30
当我们用setter方法为x或y赋值一个负数的时候,程序编译时就会抛出对应的错误信息
这样就可以对输入的数值做一定的筛查,防止引起程序的错误
在typescript中还有一种特定的写法来实现get和set的操作
interface IPoint {
drawPoint: () => void;//由于此函数没有返回值所以用void
getDistances: (p:IPoint) => number;
X:number
Y:number
}
class Point implements IPoint {
constructor(private x:number,private y:number){
};
drawPoint = () =>{
console.log("x:",this.x,"y:",this.y)
};
getDistances=(p: IPoint) => {
return Math.pow(p.X-this.x,2)+Math.pow(p.Y-this.y,2)
};
set X(value:number) {
if(value < 0){
throw new Error("X数值不能小于0")
}
this.x = value
};
set Y (value:number){
if(value < 0){
throw new Error("Y数值不能小于0")
}
this.y = value
};
get X(){
return this.x
};
get Y(){
return this.y
};
}
const point =new Point(2,3) //创建实例使用new关键词
point.X = 20
point.Y = 30
point.X //20
point.Y //30
需要注意的点是,用了这个方法以后编译器的版本至少设置为es5以上,指定版本的编译命令为**tsc -t es5 main.ts
**
2.5 Module 模块
typescript中的模块的用法跟es6相同,都是通过export导出和通过import导入。
将Point类全部转移到一个新的文件point.ts中去,并且用关键字export导出
然后使用import {Point} from './point’
在调用页面进行引入
2.6 Generics 泛型
在typescript中类型加上箭头括号就是泛型 如:Array<number>
泛型的具体用法如下
当我们为Array指定类型的时候,输入其他类型程序就会报错
而当我们把数组的泛型改为any的时候,报错虽然消失,但变量l2的类型也变为了any,那样就失去了强类型的意义
这个时候可以在函数的参数前面增加一个<T>
动态类型泛型,用T动态的替换any(大写T是一个约定写法,可以随意选用名称替代)
//写法一
let lastInArray = <T>(arr:Array<T>) =>{
return arr[arr.length-1]
}
//写法二
//let lastInArray = <T>(arr:T[]) =>{
// return arr[arr.length-1]
//}
const l1 = lastInArray<number>([1,2,3]) //l1:number
const l2 = lastInArray<string | number>(['a','b','c'])//l2:string | number
在lastInArray后写上泛型的一个好处是有可能出现混合类型的情况,如变量l2,若是此时没有写明<string | number>
的话,则程序会判定l2是一个string类型而不是一个混合类型
除了上面的但泛型以外还有多泛型的一种用法
let makeTuple =<T,Y>(x:T,y:Y) => [x,y]
const v1 = makeTuple(1,'one') //v1:(string | number)
const v2 = makeTuple<boolean,number>(true,1) //v2:(boolean | number)
同时泛型也可以指定默认类型
let makeTuple =<T,Y=number>(x:T,y:Y) => [x,y]
const v1 = makeTuple(1,'one') //v1:(string | number)
const v2 = makeTuple<boolean>(true,1) //v2:(boolean | number)