1、安装typescript
全局安装:npm install -g typescript
检查是否安装成功(出现版本号表示安装成功):tsc -v
如果使用tsc指令出现如下错误:
解决办法:
- 以管理员的身份运行vscode
- 终端执行:get-ExecutionPolicy,结果:Restricted
- 终端执行:set-ExecutionPolicy RemoteSigned
- 终端执行:get-ExecutionPolicy,结果:RemoteSigned
2、编写第一个typescript程序
- 创建ts文件:
hello_world.ts
- 编写代码:
(() => {
function sayHi(str:String){
return '你好:'+str
}
//sayHi(123) 报错: 类型错误
sayHi('bob')
let num:Number = 123
console.log(num)
})()
- 编译ts代码:
**注意:**浏览器无法识别ts代码,故需要把ts代码编译成js代码后才能被浏览器解析使用,即 **ts --> js --> 执行**
- 手动编译:
**tsc ./hello_world.ts**
- vscode自动编译:
编译ts后,会生成一个对应的js文件:
3、基本数据类型声明
基本数据类型:number、string、boolean、undefined、null、symbol、bigint
//基本数据类型声明
//注意:声明类型时,既可以用大写又可以使用它小写,如number、Number都可以
//ts编译器有自动检测类型机制,即声明变量但没有声明类型时,ts编译器会根据语法自动检测该变量的类型,故后续为改变量赋值为其他类型时会报错
//数字类型:number
let num:number = 123
//num = 'abc'//报错
num = 456
console.log(num)
//字符串类型:string
let str:string = 'you'
let str1 = 'me'
// str = 123 报错
str = 'he'
//str1 = 123 报错 自动检测类型机制
console.log(str,str1)
// 布尔类型:boolean
let flag:boolean = true
// flag = 'true' 报错
flag = false
console.log(flag)
// undefined和null:不常用
let u:undefined = undefined
let n:null = null
// u = 123 报错
// n = {} 报错
console.log(u,n)
4、引用数据类型声明(object、array)
// 引用数据类型声明
//数组:Array
//第一种方法:
let arr1:[] = [] //定义空数组,没有指定元素类型
// arr1 = [1,2,3] 报错
// arr1 = ['a','v'] 报错
arr1 = []
let arr2:number[] = [] //指定数组中的元素必须是number类型
// arr2 = ['a','v'] //报错
arr2 = []
arr2 = [1,2,3]
//第二种方法:泛型<T>
let arr3:Array<number> = [] //声明数组,并指定元素的类型为number类型
// arr3 = ['a','v'] 报错
arr3 = []
arr3 = [1,2,3]
let arr4:Array<string> = ['a','b'] //声明数组,并指定元素的类型为string类型
arr4 = []
arr4 = ['this','is','a','apple']
//Object: 表示非原始类型,除了number、string、boolean之外的类型
let obj:object = {}
// obj = 123 报错
// obj = 'abc' 报错
// obj = true 报错
// obj = null 报错 与版本有关,有的版本不会报错,以及在严格模式下也会报错
// obj = undefined 报错 与版本有关,有的版本不会报错,以及在严格模式下也会报错
obj = [] //不会报错,数组也属于对象,typeof [] === 'object' ----> true
obj = new String() //不会报错,得到的是一个实例化“对象”
obj = String //不会报错,是一个类
5、声明any和void类型
//any和void类型声明:
//any:表示任何类型
let test:any = 123
test = 'abc'
test = true
test = false
test = {}
test = []
let newArr:any[] = [100,1,3,'this',false,true]
// console.log(newArr[0].split(''))
//报错:newArr[0].split is not a function
//原因:newArr[0]是100,这是一个数字类型,数字类型没有split方法
// any的优点:在我们不知道返回值类型的时候,使用any声明类型,就能避免报错
// any的缺点:在我们需要操作变量时,由于无法确定值类型,所以可能出现错误的情况
//void:表示没有类型,一般用于声明函数没有返回值,声明变量没有意义
//声明函数:
function demo():void{
console.log(123)
}
console.log(demo()) //undefined
//声明变量:
let u1:void = undefined
// let u2:void = null 报错
// let u3:void = 12 报错
6、类型推论
类型推论:如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型
let test1 = 123 //ts会依照类型推论的规则推断出test1是一个number类型
// test1 = 'abc' 报错
let test2 //ts会依照类型推论的规则推断出test2是一个any类型
test2 = 123
test2 = 'this'
test2 = false
7、联合类型
**联合类型:**表示取值可以为多种类型中的一种
注意:
- 联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
- 只能访问此联合类型的所有类型里共有的属性或方法
let test3: number | boolean = true //变量test3的值只能是number或者boolean
test3 = 1
test3 = false
// test3 = 'test' 报错
let test4:number | boolean | string = false
test4 = 123
// console.log(test4.split('')) 报错,原因:当前的变量test4是一个number类型,没有split方法
test4 = 'this is a apple'
console.log(test4.split(''))
test4 = false
// console.log(test4.split('')) 报错,原因:当前的变量test4是一个boolean类型,没有split方法
console.log(test4.toString()) //toString(),是number、string、boolean类型共有的方法
8、接口
对象类型
**接口:**可以理解为是一种约束,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
注意:
- 接口一般首字母大写,有的编程语言中会建议接口的名称加上 I 前缀
- 定义的变量比接口少一些属性和多一些属性都是不允许的(赋值的时候,变量的形状必须和接口的形状保持一致)
interface IPerson {
name:string;
age:number;
height:string;
}
let tom:IPerson = {
name:'Tom',
age:18,
height:'180cm',
// sex:'男'报错
}
可选属性:
通过 ? 来定义,可选属性可以被定义也可以不被定义
interface IStudent {
name:string;
age:number;
sex?:string; //表示sex是一个可选属性
}
let bob:IStudent = {
name:'bob',
age:23,
sex:'男',
// width:'100cm' 报错,不可以添加属性
}
let john:IStudent = {
name:'john',
age:20
}
任意属性:
注意:
- 一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
- 一个接口中只能定义一个任意属性
interface IPeople {
name:string;
count?:number;
[propName:string]:any; //定义任意类型
}
let one:IPeople = {
name:'团体',
address:'china',
phone:'10086'
}
console.log(one)
//注意:
//一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
//一个接口中只能定义一个任意属性
interface Person {
name: string;
age: number;
[propName: string]: string | number;
}
let wick: Person = {
name: 'wick',
age: 25,
gender: 'male',
};
可读属性:
使用readonly定义
**注意:**只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
interface IPerson1 {
readonly id:number;
name:string;
}
let wenston:IPerson1 = {
id:10,
name:'wenston'
}
// wenston.id = 1235 报错,只读属性不允许更改
数组类型
interface INewArray {
[index:number]:number
}
let newArr1:INewArray = [1,2,3]
// let newArr2:INewArray = [1,2,'abc'] 报错
函数类型
interface ISearchFun {
// (变量名:类型,...):返回值类型
(a:string,b:string):boolean
}
const fun1:ISearchFun = function(a:string,b:string):boolean{
return a.search(b) !== -1
}
9、函数定义
函数声明:
function sum(a:number,b:number):number{
return a + b
}
函数表达式:
let sum1 = function(a:number,b:number):number{
return a+b
}
函数完整写法:
let sum2:(a:number,b:number)=>number = function(a:number,b:number):number{
return a+b
}
可选参数:
通过 ?定义
**注意:**可选参数不能放在必选参数之前,如:y不能放在x之前
let outStr = function(x:string,y?:string):string{
return x + y
}
console.log(outStr('你好'))
console.log(outStr('你好','龙傲天'))
// 报错:变量z会报错,原因:可选参数不能放在必选参数之前
// let outStr1 = function(x:string,y?:string,z:string):string{
// return x + y
// }
默认参数:
**注意:**默认参数可以放在可选和必选参数之后
let outStr2 = function(x:string,y?:string,z:string = '张三'):string{
return x + y + z
}
console.log(outStr2('你好'))
let outStr3 = function(x:string,z:string = '李四',y?:string):string{
return x + y + z
}
console.log(outStr3('你好'))
剩余参数:
通过 …rest 实现
let outInfo = function(a:string,b:string,...args:string[]){
console.log(a,b,args)
}
// outInfo('a','b',1,2,3,4)//报错
outInfo('a','b','this','is','my','wife')
函数重载:
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理
//实现一个需求:当参数x、y都是数字类型时进行相加,当x、y都是字符串类型时进行字符串拼接
//函数实现:
//然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串
function concatFun1(x:string | number,y:string | number):string | number{
if(typeof x === 'string' && typeof y === 'string'){
return x + y //字符串拼接
}else if(typeof x === 'number' && typeof y === 'number'){
return x + y //求和
}else{
return '情况不存在'
}
}
//将鼠标放在下面两个函数调用上,代码提示不够准确,代码提示:
//function concatFun1(x:string | number,y:string | number):string | number
console.log(concatFun1(1,2))
console.log(concatFun1('this','is'))
//函数重载实现:
function concatFun2(x:string,y:string):string;
function concatFun2(x:number,y:number):number;
function concatFun2(x:string | number,y:string | number):string | number{
if(typeof x === 'string' && typeof y === 'string'){
return x + y //字符串拼接
}else if(typeof x === 'number' && typeof y === 'number'){
return x + y //求和
}else{
return '情况不存在'
}
}
//在编辑器的代码提示中,可以正确的看到前两个提示
console.log(concatFun2(1,2)) // 代码提示:function concatFun2(x:number,y:number):number
console.log(concatFun2('this','is'))// 代码提示:function concatFun2(x:string,y:string):string
10、类型断言
**定义:**手动指定一个值的类型
语法:
值 as 类型
<类型>值
**注意:**类型断言并不是将函数强制转换为对应的类型
// function getLength(x:string | number):number{
// if(x.length){ //报错,因为length属性只有string类型值才拥有,但变量x不能确定是string还是number
// return x.length //报错,同上
// }else{
// return x.toString().length
// }
// }
//使用类型断言解决:
function getLength(x:string | number):number{
if((x as string).length){
console.log('1')
return (<string>x).length
}else{
console.log('2')
return x.toString().length
}
}
console.log(getLength('123'))
console.log(getLength(123))
注意:
- 将任何一个类型断言为 any,any类型是可以访问任何属性和方法的
- 它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any
- 一方面不能滥用 as any,另一方面也不要完全否定它的作用,我们需要在类型的严格性和开发的便利性之间掌握平衡
将任何一个类型断言为any:
// window.a = 10 报错,我们需要在window上添加一个a属性,但typescript提示window中没有a属性
//将window断言为any类型,any类型可以访问任何属性和方法
(window as any).a = 10
将any断言为一个具体类型:
//将any 断言为一个具体类型
function sumTestFun(x:any,y:any):any{
return x + y
}
//传递的参数都为数值类型,所以可以断言返回的值是数值类型
let a12 = sumTestFun(1,2) as number // a --> 数值类型
//传递的参数都为字符串类型,所以可以断言返回的值是字符串类型
let b12 = sumTestFun('1','2') as string // b --> 字符串类型
11、类型别名
使用 type 定义,一般用于定义联合类型
type s = string //s 代表了 string
//如下:a和b都是string类型
let a:string = 'this'
let b:s = 'is'
type snb = string | number | boolean
//test和test1 都是 string、number、boolean中的一种类型
let test: string | number | boolean = 'this is'
let test1:snb = 'my wife'
12、字符串字面量类型
使用 type 定义,约束取值只能是某几个字符串中的一个
type name = '张三' | '张伟' | '张益达'
let babyName:name = '张伟' //只能取 name中的其中一个
// let babyName1:name = '张根硕' 报错
13、元组(Tuple)
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象
let arr:number[] = [1,2,3,4] //这里指定了数组中的元素只能是number类型
//如果想数组中的元素可以有多种类型,可以使用元组定义:
//通过元组指定了第一个元素的类型为number,第二个元素的类型为string,必须保持一致
let Tarr:[number,string] = [123,'true']
//添加元素的时候,需要是元组中指定的类型,如上便是number或string类型
Tarr.push('456')
Tarr.push(456)
// Tarr.push(true) 报错,原因:添加的元素是boolean类型
//可以只赋值其中一项
let Ttom:[string,number];
// Ttom[0] = 123 报错
Ttom[0] = 'abc'
//当直接对元组类型的变量进行初始化或赋值时,需要提供所有元组类型中指定的项
let Twick:[string,number]
// Twick = ['123'] 报错
Twick = ['123',123]
14、枚举(Enum)
- 枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等
- 枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射(枚举名和枚举值作为键值对,可以相互获取)即:枚举[枚举名] — 枚举[枚举值]
- 未手动赋值的枚举项会接着上一个枚举项递增
普通枚举:
enum Days {
one,
two,
three
}
console.log(Days.one)
console.log(Days)//{ '0': 'one', '1': 'two', '2': 'three', one: 0, two: 1, three: 2 }
手动赋值:
enum Days1 {
one = 7,
two,
three,
four
}
//由下面的代码可得:未手动赋值的枚举项会接着上一个枚举项递增
console.log(Days1.one)// 7
console.log(Days1.two)// 8
console.log(Days1.three)// 9
//如果出现相同枚举值,后面的枚举值会覆盖前面的枚举值
enum Days2 {
one,
two,
three,
four=1,
}
console.log(Days2)//{'0': 'one','1': 'four','2': 'three',one: 0,two: 1,three: 2,four: 1}
常数项和计算所得项:
//其中red为常数项,blue为计算所得项
enum color {
red,
blue='blue'.length,
}
//如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错,如下:
enum color1{
red,
blue='blue'.length,
// green, 报错
green=1
}
常数枚举:
使用const enum定义
常数枚举与普通枚举的区别: 常数枚举会在编译阶段被删除,并且不能包含计算成员
const enum Direction{
up,
down=9,
left,
right,
// to='to'.length 报错
}
console.log(Direction.up) // 0 /* Direction.up */
console.log(Direction.down)// 9 /* Direction.down */
console.log(Direction.left)// 10 /* Direction.left */
外部枚举:
使用 declare enum 定义,常用于声明文件中
外部枚举与普通枚举的区别: 外部枚举会用于编译时的检查,编译结果中会被删除,并且不能包含计算成员
declare enum Direction1{
up,
down,
left,
right=9,
// to='to'.length 报错
}
// console.log(Direction1.up)
// console.log(Direction1.right)
//同时使用 declare 和 const 也是可以的:
declare const enum Direction2{
up,
down,
left,
right
}
console.log(Direction2.down)// 1 /* Direction2.down */
console.log(Direction2.right)// 3 /* Direction2.right */
15、类
属性和方法的定义:
class Person{
name:string
age:number
constructor(name:string,age:number){
this.name = name
this.age = age
}
sayHi(str:string):void{
console.log('hi ',str)
}
}
let p = new Person('张三',18) //new会执行constructor构造函数
p.sayHi('李四')
类的继承:
- 通过extends实现继承功能
- 子类中通过super()调用父类的构造函数constructor
- 子类中可以使用父类的属性和方法,通过super调用父类的方法,this调用父类的属性
- 字类可以重写父类的方法
//父类:
class Animal {
name:string
age:number
constructor(name:string,age:number){
this.name = name
this.age = age
}
outInfo(){
console.log('我是一只动物')
}
sayHi(str:string){
console.log('Hi',str)
}
}
//子类:
class Dog extends Animal{
color:string
constructor(name:string,age:number,color:string){
//通过super调用父类Animal的构造函数
super(name,age)
this.color = color
}
//重写父类Animal的sayHi方法
sayHi(str:string):void{
//通过super调用父类Animal的方法
super.outInfo()
console.log(`${str},我的名字是${this.name},我已经${this.age}岁了,我的颜色是${this.color}`)
}
}
let dog = new Dog('拉布拉多',3,'白色')
dog.sayHi('大家好')
类的存取器:
class Name{
firstName:string
lastName:string
constructor(firstName:string,lastName:string){
this.firstName = firstName
this.lastName = lastName
}
//设置存取器:
//读取器
get fullName(){
return this.firstName + ' - ' + this.lastName
}
//设置器
set fullName(value){
let names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
let zs = new Name('张','三')
console.log(zs.fullName)
zs.fullName = 'john-wick'
console.log(zs.fullName)
类的静态成员:
- 通过static定义
- 静态属性和方法只属于类自己,即可以通过类调用,不能通过实例化对象调用
- 在类中,不能使用this调用静态属性和方法
class Cat{
static color:string = '红色'
constructor(){
// this.color = '白色' //color是静态属性,不能通过this操作
}
sayHi(str:string){
console.log(str)
}
static outInfo(str:string):string{
return `我是一只猫,${str}`
}
}
let cat = new Cat()
console.log(Cat.color) //红色
// console.log(cat.color) //报错,实例化对象cat上不存在color属性
console.log(Cat.outInfo('yes'))
cat.sayHi('你好')
// cat.outInfo('yes')//报错,实例化对象cat上不存在outInfo方法
类的访问修饰符:
- public:修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private:修饰的属性或方法是私有的,不能在声明它的类的外部访问,包括子类中也不可以被访问
- protected:修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中是允许被访问的
总结:
- public:没有限制,在任何地方都可以被访问到
- private:只能在当前类中访问,其他任何地方都不能被访问到
- protected:只能在当前类和子类中访问,其他任何地方都不能被访问到
class Man{
public name:string
private age:number
protected gender:string
constructor(name:string,age:number,gender:string){
this.name = name
this.age = age
this.gender = gender
}
public sayName(){
console.log('my name is '+this.name)
}
private sayAge(){
console.log('my age is '+this.age)
}
protected sayGender(){
console.log('my gender is '+this.gender)
}
}
class M extends Man{
constructor(){
super('张三',18,'男')
}
outInfo(){
console.log(`姓名:${this.name}`)
// console.log(`年龄:${this.age}`)//报错,age是私有属性(private),只能在Man类中使用
console.log(`性别:${this.gender}`)// gender是保护属性(protected),只能在Man类和其子类M中使用
super.sayName()
// super.sayAge() 报错
super.sayGender()
}
}
let man = new Man('李四',23,'男')
console.log(man.name)
// console.log(man.age)//不能访问
// console.log(man.gender)//不能访问
let m = new M()
console.log(m.name)
m.outInfo()
m.sayName()
参数属性:
修饰符(public、private、protected)和readonly可以用在构造函数参数中
作用:等同于在类中定义该属性,同时为该属性赋值,使代码更简单
readonly:关键字,表示只读,只允许出现在属性声明或索引签名或构造函数中
注意:
- 只读属性只能在构造函数中修改
- readonly和其他修饰符同时使用时,readonly需要放在后面,如:public readonly
class Test{
readonly name:string
constructor(name:string){
this.name = name
}
}
let t = new Test('李四')
console.log(t.name)
class Test1{
//readonly age:number 会在类中创建一个age:number属性,并且会在构造函数中为age赋值,即:this.age = age
constructor(readonly age:number){
}
//等同于:
// readonly age:number
// constructor(age:number){
// this.age = age
// }
}
let t1 = new Test1(18)
console.log(t1.age)
class Test2{
constructor(public age:number){
}
}
let t2 = new Test2(233)
console.log(t2.age)
class Test3{
constructor(private age:number){
}
//等同于:
// private age:number
// constructor(age:number){
// this.age = age
// }
}
let t3 = new Test3(54)
// console.log(t3.age) 报错,无法访问age,因为这里的age是一个私有属性
抽象类:
- 使用 abstract 定义抽象类和其中的方法、属性
- 抽象类是不允许被实例化
- 抽象类中的抽象属性和抽象方法,在子类中必须被实现
- 抽象类中抽象属性不能被初始化值,也不能在构造函数中被访问
- 抽象类中的抽象方法只能定义,但不能被实现
abstract class Test4{
abstract color:string
// constructor(color:string){
// //不能在构造函数中访问类的抽象属性
// // this.color = color 报错
// }
abstract sayHi()//抽象类中的抽象方法不能被实现,只能被子类实现
}
class Test5 extends Test4{
color:string
age:number
constructor(color:string,age:number){
super()
this.color = color
this.age = age
}
sayHi() {
console.log('hi')
}
outInfo(){
console.log('输出一些信息')
}
}
let test5 = new Test5('白色',18)
console.log(test5.color)
console.log(test5.color)
test5.sayHi()
test5.outInfo()
类的类型:
与接口类似
class Car{
color:string
constructor(color:string){
this.color = color
}
}
class Bmw extends Car{
constructor(color:string){
super(color)
}
}
//因为Bmw继承于Car,所以可以使用Car指定bmw的类型,因为Bmw没有在自身类中添加其他属性和方法,
//故Bmw与Car是保持一致的,所以可以使用Bmw指定car_1的类型
let car_1:Bmw = new Car('白色')
let bmw:Car = new Bmw('黑色')
class Benz extends Car{
name:string
constructor(color:string,name:string){
super(color)
this.name = name
}
}
//因为Benz是继承于Car,所以可以使用Car指定benz的类型,因为Benz自身上有一些其他属性和方法,
//故Benz和Car并不完全一致,所以不能使用Benz指定car_2的类型
let benz:Car = new Benz('红色','C300L')
// let car_2:Benz = new Car('绿色') 报错
16、类与接口
有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces)
用 implements 关键字来实现
接口定义与实现:
interface ISing{
name:string
sing()
}
interface IDance{
dance()
}
interface IRap{
rap()
}
interface IBaseketball{
playBaseketball()
}
//一个类实现了一个或多个接口,那么所有接口中的函数和属性都需要被实现,否则会报错
//接口定义中,只需要定义,不需要实现
class Ji implements ISing,IDance,IRap,IBaseketball{
name: string;
constructor(name:string){
this.name = name
}
sing() {
console.log('唱')
}
dance() {
console.log('跳')
}
rap() {
console.log('rap')
}
playBaseketball() {
console.log('篮球')
}
sayName(){
console.log('练习时长两年半的偶像练习生')
}
}
class Cxk implements ISing,IDance,IRap,IBaseketball{
name: string;
constructor(name:string){
this.name = name
}
sing() {
console.log('唱')
}
dance() {
console.log('跳')
}
rap() {
console.log('rap')
}
playBaseketball() {
console.log('篮球')
}
sayName(){
console.log('蔡徐坤')
}
}
接口继承接口:
- 接口之间可以继承,而且可以多继承
- 可以避免类需要实现多个接口,如果使用一个接口继承其他的多个接口,那么类只需要继承这一个接口便可以了,效果与类中实现多个接口一致
interface IColor{
outColor(name:string)
}
interface IHeight{
outHeight()
}
interface IWidth extends IColor,IHeight{
outWidth()
}
class Monkey implements IWidth{
height:string
width:string
constructor(height:string,width:string){
this.height = height
this.width = width
}
outColor(name:string):string {
return name
}
outHeight() {
return this.height
}
outWidth() {
return this.width
}
}
let monkey = new Monkey('170cm','23cm')
console.log(monkey.outColor('黄色'))
console.log(monkey.outHeight())
console.log(monkey.outWidth())
接口继承类:
- 接口只能继承类的属性和方法(实例属性和实例方法)
- 构造函数、静态属性、静态方法都是不会继承的
class NewPerson {
name:string
constructor(name:string){
this.name = name
}
sayHi(){
console.log('hi')
}
}
interface IPersonTest extends NewPerson{
address:string
run()
}
let person:IPersonTest = {
name:'张三',
address:'中国',
sayHi(){
console.log('hello')
},
run() {
console.log('跑步')
},
}
17、声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型
函数合并:
可以使用函数重载定义多个函数类型
function concatFun2(x:string,y:string):string;
function concatFun2(x:number,y:number):number;
function concatFun2(x:string | number,y:string | number):string | number{
if(typeof x === 'string' && typeof y === 'string'){
return x + y //字符串拼接
}else if(typeof x === 'number' && typeof y === 'number'){
return x + y //求和
}else{
return '情况不存在'
}
}
//在编辑器的代码提示中,可以正确的看到前两个提示
console.log(concatFun2(1,2)) // 代码提示:function concatFun2(x:number,y:number):number
console.log(concatFun2('this','is'))// 代码提示:function concatFun2(x:string,y:string):string
接口合并:
interface IA{
price:number
}
interface IA{
count:number
}
//等同于:
// interface IA{
// price:number
// count:number
// }
let aTest:IA = {
price:100,
count:99
}
interface IB{
price:number,
}
interface IB{
// price:string 接口合并时,合并的属性的类型必须是相同的,所以这里的price应该是number类型
price:number
count:number
}
let bTest:IB = {
price:12,
count:12
}
interface IC{
name:'小白'
age:number
}
interface IC{
// name:'小红' 接口合并时,合并的属性的类型和字面量(值)必须是相同的,所以这里的name应该是’小白‘,而不是’小红‘
name:'小白'
age:number
}
let cTest:IC = {
name:'小白',
age:18
}
interface ID{
width:string
getColor(color:string)
}
interface ID{
height:string
getColor(color:string,temp:number)
}
//等同于:
interface ID{
height:string
width:string
getColor(color:string)
getColor(color:string,temp:number)
}
类合并:
类的合并与接口的合并规则一致
18、泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function createArr(length:number,value:any):any[]{
let result = []
for(let i=0;i<length;i++){
result[i] = value
}
return result
}
console.log(createArr(10,'this'))
//上面这段代码进行编译不会报错,但是有一个缺陷,
//返回值是一个数组,但是里面元素的类型可以是任意类型,
//预期是返回数组中的元素类型与value类型一致
//使用泛型实现:
//T : 表示任意类型;如下代码可见,返回数组中的元素类型与value的类型始终保持一致
function createNewArr<T>(length:number,value:T):Array<T>{
let result = []
for(let i = 0;i< length;i++){
result[i] = value
}
return result
}
//调用时可以指定它的具体类型为string或其他,也可以不手动指定,而让类型推论自动推算出来
console.log(createNewArr<string>(10,'this'))//value和返回的数组元素的类型都为string
console.log(createNewArr(5,0))//value和返回的数组元素的类型都为number(类型推论)
多个类型参数:
定义泛型时,可以定义多个类型参数
function swap<T,U>(tuple:[T,U]):[U,T]{
return [tuple[1],tuple[0]]
}
console.log(swap([true,'this']))
泛型约束:
//在函数内部使用泛型变量时,由于事先不知道泛型的具体类型,不能随意操作它的属性和方法
function outVal<T>(arg:T):T{
// console.log(arg.length) 报错,不知道泛型的具体类型,所以无法确定arg是否具有length属性
return arg
}
//使用泛型约束,只允许这个函数传入那些包含length属性的变量:
interface ILengthWise{
length:number
}
//由于 T 继承了 ILengthWise接口 ,所以传递的参数arg中必须包含(实现)接口中的length属性,
//如果没有,在编译阶段便会报错
function outNewVal<T extends ILengthWise>(arg:T):T{
console.log(arg.length)
return arg
}
console.log(outNewVal('this is'))
//多个类型参数之间也可以相互约束
//这样U中就不会出现T中不存在的字段
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id];
}
return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });
// copyFields(x, { b: 5, d: 9, e:8}); //报错,第二个参数中的属性,必须在x中存在
泛型接口:
使用一个包含泛型的接口,来定义函数的形状
interface ICreateArr{
<T>(length:number,value:T):Array<T>
}
let newCreateArr:ICreateArr = function <T>(length:number,value:T):Array<T>{
let result:T[] = []
for(let i=0;i<length;i++){
result[i] = value
}
return result
}
console.log(newCreateArr(5,true))
console.log(newCreateArr<boolean>(5,false))
//将泛型参数提前到接口名上:
//注意:如将泛型参数放到接口名后定义,那么在使用接口的时候就需要定义泛型的类型
interface ICreateNewArr<T>{
(length:number,value:T):Array<T>
}
let newCreateNewArr:ICreateNewArr<string> = function <T>(length:number,value:T):T[]{
let result:T[] = []
for(let i=0;i<length;i++){
result[i] = value
}
return result
}
//报错,因为在使用接口ICreateNewArr时,已经定义了泛型的类型为string,
//所以这里只能传递string类型的参数
// console.log(newCreateNewArr(5,false))
console.log(newCreateNewArr(5,'are'))
泛型类:
与泛型接口类似
class CreatTest<T>{
value:T
add:(x:T,y:T) => T
}
let ct = new CreatTest<number>()
// ct.value = 'this' 报错,'this'是string类型,而value需要的是number类型
ct.value = 1
//报错,x、y需要的是number类型,返回值的类型也应该是number类型
// ct.add = function(x:string,y:string){ return x + y }
//报错,返回值需要与 T 保持一致,即返回值需要时number
// ct.add = function(x:number,y:number){return ''}
ct.add = function(x:number,y:number){return x+y}
泛型参数的默认类型:
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length:number,value:T):T[]{
let result:T[] = []
for(let i=0;i<length;i++){
result[i] = value
}
return result
}