typeScript
是 JavaScript
的一个超集, typeScript
就是在JavaScript
上做了一层封装,当然最终代码可以编译为 JavaScript
。typeScript
增加了代码的可读性和可维护性,即使 typeScript
编译报错,也可以生成 JavaScript
文件。
安装typeScript
全局安装
npm install -g typescript
使用命令行tsc xxx.ts
来编译ts文件
在JavaScript 中类型分为两种:原始数据类型和对象类型
原始数据类型包括:布尔值、数值、字符串、null
、undefined
以及 ES6 中的 Symbol
布尔值
布尔值是最基本的数据类型,在ts
中使用boolean
来定义布尔类型
let isDone: boolean = false;
let isDone: boolean = new Boolean(1); //注意,使用new Boolean()创造的对象不是布尔值
//Type 'Boolean' is not assignable to type 'boolean'. 'boolean' is a primitive,
//but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
let isDone: boolean = Boolean(1);//直接调用Boolean,返回的是一个布尔类型
数字类型
使用 number
去定义 除了支持十进制和十六进制,TypeScript还支持二进制和八进制。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的0b表示二进制
let binaryLiteral: number = 0b1010;
// ES6 中的0o表示八进制
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
编译结果:
var decLiteral = 6;
var hexLiteral = 0xf00d;
var binaryLiteral = 10;
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;
二进制和八进制编译时会转化成十进制
字符串
使用string
来定义
let name: string = "小明"
// 模板字符串
let hello = `Hello, my name is ${myName}`
Null 和 Undefined
使用 null
和 undefined
来定义这两个原始数据类型
let u: undefined = undefined;
let n: null = null;
默认情况下null
和 undefined
是所有类型的子类型
// 这样不会报错
let num: number = undefined;
任意值
我们想要为那些在编程阶段还不清楚类型的变量指定一个类型,比如用户输入的,或者第三库的代码,可以使用any
类型来标记这些变量
如果是普通类型,在赋值过程中改变类型是不允许的
let n: number = 1
n = "小明" //Type '"小明"' is not assignable to type 'number
如果是any
类型,在赋值过程中改变类型是允许的
let n: any= 1
n = "小明"
在任意值上访问任何属性调用任何方法都是允许的
let n: any= 1
console.log(n.name)
console.log(n.getData())
变量在声明过程中,未指定类型,那么他会识别成任意类型
let n;
n = 1
n = "小明"
void
void
类型像是与any类型相反
,它表示没有任何类型,当一个函数没有返回值时,你会看到他的返回类型是void
function getData(): void {
console.log("getData")
}
声明一个void
类型的变量没什么意义,他只能赋值undefined
,null
let v: void = undefined;
v = null
与 undefined
和null
类型相似,他们之间的区别是undefined
和null
类型相是所有类型的子类型
let n:number = 1
let u:void = null ;
// 这样不会报错
n = null
n = undefined
//这样会报错
n = v //Type 'void' is not assignable to type 'number'.
联合类型
值可以为多种类型中的一种,使用|
来分隔每个类型
let a: number | string ;
// 表示 允许 a的类型是number 或者 string,但是不能是其他类型
a = "小明"
a = 18
a = true //报错
当 ts
不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法
function getLength(str: number | string ){
console.log(str.length)
}
//报错
因为length
不是 string
和 numbe
的共有属性,所以会报错
访问string
和 numbe
的共有属性是没问题的
function getLength(str: number | string ){
str.toString()
}
联合类型在变量赋值的时候,会根据类型推论的规则推断出一个类型
let a:number |string;
a = "小明"
console.log(a.length)
a = 13
console.log(a.length) //报错 Property 'length' does not exist on type 'number'.
类型推论
ts
会在没有明确的指定类型的时候推测出一个类型,这就是类型推论
let a = 7// => let a: number= 7
a = "小明" // 报错
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any
类型,而不被类型检查
let a; // => let a: any;
a = 7
a = "小明"
接口
在ts
中,我们用接口(Interfaces
)来定义对象类型
interface Person {
age:number,
name: string
}
let xiaoming: Person = {
age:18,
name:"小明"
}
上述代码我们定义了一个接口Person
,然后定义了一个类型是Person
的变量xiaoming
,
这样,我们就约束了变量xiaoming
的形状要与接口Perso
一致。接口的首字母尽量大写,
定义的变量比接口少一些属性或者多一些属性都是不允许的。
有时候我们需要某些属性可有可无的,这时候我们可以用到可选属性
,用?
来表示,
interface Person {
name: string,
age?:number,
}
let xiaoming: Person = {
name:"小明"
}
let zhangsan: Person = {
name:"张三",
age: 18
}
可选属性,表是该属性可以不存在,这时候还是不能添加未定义的属性
如果我们想要添加未定义的属性,我们使用 任意属性
来实现
interface Person {
name: string,
age?:number,
[name:string]:any
}
let xiaoming: Person = {
age:18,
name:"小明",
height: 170,
}
[name:string]:any
表示定义了一个值为any
的任意属性
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person {
name: string,
age?:number,
[name:string]:string
}
let xiaoming: Person = {
age:18,
name:"小明",
height: '170cm',
}
//报错
//类型“number”的属性“age”不能赋给字符串索引类型“string”
因为任意属性的值允许类型是string
,而age
的值类型是number
,number
类型不是任意属性值允许类型的子类型
一个接口中只能定义一个任意属性,可选属性可以是多个。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型
interface Person {
name: string,
age?:number,
[name:string]:string | number
}
let xiaoming: Person = {
age:18,
name:"小明",
height: '170cm',
}
有时候我们希望对象中的一些字段只能在创建的时候被赋值,我们可以用readonly
来实现
interface Person {
readonly name: string,
age?:number,
[name:string]:string | number
}
let xiaoming: Person = {
name:"小明",
age:18,
height: '170cm',
}
xiaoming.name = "张三" //报错 无法分配到 "name" ,因为它是只读属性
只读是约束在第一次给对象赋值,而不是第一次给只读属性赋值
数组
在ts
中,数组的定义方式有多种,
- 最简单的是
类型[]
let arr: number[] = [1,2,3,4]
不允许其他类型出现,undefined
和null
除外(undefined
和null
是所有类型的子类型)
let arr: number[] = [1,2,3,4,"5"] //报错 不能将类型“string”分配给类型“number”
- 也可以用数组泛型
Array<类型>
来表示数组
let arr: Array<number> = [1,2,3,4]
- 当然也可以用接口来描述数组,一般不用这种方式,因为没有前两种简单,常用他来表示类数组,类数组不是数组类型,比如
arguments
function todo(){
let arg:Array<number> = arguments
}
// 已声明“arg”,但从未读取其值。ts(6133)
//类型“IArguments”缺少类型“number[]”的以下属性: pop, push, concat, join 及其他 24 项。ts(2740)
使用接口描述数组
interface ITodo {
[index: number]: any;
length: number;
callee: Function;
}
function todo(){
let arg:ITodo = arguments
}
函数
函数的表达式常见的有两种:函数声明,函数表达式
//函数声明
function todo(){
....
}
//函数表达式
let todo = function(){
...
}
函数申明和函数表达式的主要区别:函数声明整体会被提升到当前作用域的顶部,函数表达式也提升到顶部但是只有其变量名提升
函数是有输入和输出的,在ts
中要对其进行约束,函数声明的类型定义如下
function sum(a: number, b: number): number{
return a+b
}
sum(1,1)
上述例子是,参数a
是number
类型,参数b
是number
类型,函数返回值是number
类型,函数多传入或者少传入参数都是不允许的
函数表达式的声明如下
let sum:(a: number, b: number)=> number = function(a: number, b: number): number{
return a+b
}
在 ts
的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
有些参数是可选的,前面提了多传入或者少传入参数都是不允许的,这时我们可以用可选参数?
来解决
function sum(a:string,b?:string):string{
return b?a+b:a
}
sum('xiao')
注意,可选参数后面不能有必要参数
有时候某些参数需要默认值,
function sum(a: number, b: number=1): number{
return a+b
}
sum(1)
//没有可选参数的那种限制
function sum1(a: number=1, b: number): number{
return a+b
}
sum1(undefined,1)
类型断言
类型断言就是用来指定值的类型,比如将一个联合类型指定成其中一个类型
语法
值 as 类型
或者
<类型>值
当 TypeScript
不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法
interface Tom {
name: string;
run(): void
}
interface Jack {
name: string;
todo():void
}
function getName(people:Tom|Jack){
console.log(people.name)
}
有时候,我们需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如
interface Tom {
name: string;
run(): void
}
interface Jack {
name: string;
todo():void
}
function getName(people:Tom|Jack){
if(typeof people.run === "function"){
....
}
}
//这时候会报错
//类型“Tom | Jack”上不存在属性“run”。类型“Jack”上不存在属性“run”
这时候我要就要用类型判断,将people
类型指定为Tom
interface Tom {
name: string;
run(): void
}
interface Jack {
name: string;
todo():void
}
function getName(people:Tom|Jack){
if(typeof (people as Tom).run === "function"){
....
}
}
类型判断只能ts
编译器,无法避免运行错误,不能滥用
interface Tom {
name: string;
run(): void
}
interface Jack {
name: string;
todo():void
}
let tom:Tom = {
name:"tom",
run(){console.log('我是tom')}
}
function getName(people:Tom|Jack){
(people as Jack).todo()
}
getName(tom)
//报错 people.todo不是个函数
getName
函数接收的参数类型是Tom|Jack
,people as Jack
隐藏了people
为Tom
的情况,直接将people
的类型断言成Jack
,ts
编辑相信了这个断言,故在调用 todo()
时没有编译错误。
将任何类型断言成any
当我们引用一个在此类型上不存在的属性或方法时,就会报错
let n: number = 1
n.length
//类型“number”上不存在属性“length”
这样的报错对我们来说很有用,
有时候,我们知道之这段代码运行绝对没有问题
window.i = 1//类型“Window & typeof globalThis”上不存在属性“i”
上诉代码ts
编辑器提示window
上不存在这个属性,我们可以将window
断言成any
来解决
(window as any).i = 1
在 any 类型的变量上,访问任何属性都是允许的
泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
举个例子
function getArray(length:number,value:any):Array<any> {
let arr:Array<any> = []
for(let i = 0 ;i < length ;i++){
arr.push(value)
}
return arr
}
getArray(3,"3") //["3","3","3"]
这段代码,编译没有问题,但是他没有定义一个准确的返回类型,Array<any>
允许数组的每一项为任意类型,我们的预期是数组的每一项都是我们输入的value
的类型,这时候我们就可以用泛型l来实现我们想要的
function getArray<T>(length:number,value:T):Array<T> {
let arr:Array<T> = []
for(let i = 0 ;i < length ;i++){
arr.push(value)
}
return arr
}
getArray<string>(3,"3") //["3","3","3"]
我们在函数名后添加了<T>
,其中 T
用来指输入的类型可以为任意类型,在后面的输入 value: T
和输出Array<T>
中即可使用了,getArray<string>
这个指定我们输入的类型,当然我们也可以不用指定,让类型推断来推断输入类型
function getArray<T>(length:number,value:T):Array<T> {
let arr:Array<T> = []
for(let i = 0 ;i < length ;i++){
arr.push(value)
}
return arr
}
getArray(3,"3") //["3","3","3"]
有时候我们的函数有多个类型,定义泛型的时候也可以定义多个参数类型
function people<T,U>(name:T,age:U): [T, U]{
return [name,age]
}
people("张三", 18)
在使用泛型的时候,因为不知道他是什么类型,所以不能随意操作他的方法或者属性
function getLength<T>(str:T): T{
console.log(str.length)
return str
}
getLength("xiaoming")
//报错 类型“T”上不存在属性“length”
这时候我们就要对泛型进行约束,这个函数只允许接收含有length
的属性的变量
interface Ilength {
length:number
}
function getLength<T extends Ilength >(str:T): T{
console.log(str.length)
return str
}
getLength("xiaoming")
我们使用了extends
来对T
进行约束,只允许输入和接口Ilength
结构相同的数据,如果输入变量不包含length
属性,ts
编译阶段就会报错
interface Ilength {
length:number
}
function getLength<T extends Ilength >(str:T): T{
console.log(str.length)
return str
}
getLength(112) //报错 类型“number”的参数不能赋给类型“Ilength”的参数
当然多个参数类型之间也可以相互约束
function peoples<T extends U,U>(obj:T,obj1:U): T{
return obj
}
peoples({name:"张三",age: 18},{name:"李四"})
泛型参数可以有默认值
function getArray<T = string>(length:number,value:T): Array<T> {
let arr:Array<T> = []
for(let i = 0 ;i < length ;i++){
arr.push(value)
}
return arr
}
泛型接口
之前我们可以用接口来定义函数
interface IlengthFn {
(str:string):number
}
let hetLength:IlengthFn
hetLength = function(str:string):number{
return str.length
}
当然也可以用泛型接口来定义函数
interface IlengthFn <T>{
(str: T):number
}
interface IIlength{
length:number
}
let hetLength:IlengthFn<any>
hetLength = function<T extends IIlength>(str:T):number{
return str.length
}