一文教你V3+TS(保姆级教程)

发布时间:2024年01月18日
TS 与 JS 的区别

基础类型
ts 的常用类型

ts 的常用基础类型分为两种: js 已有类型

  1. 原始类型:number/string/boolean/null/undefined/symbol

  2. 对象类型:object(包括,数组、对象、函数等对象)

  3. 每次编写前需要在script中添加以下代码,来编写ts语言

  4. undefined 和 nul1 都可以作为其他类型的子类型,把undefined 和nu11 赋值给其他类型的变量的,如:number类型的变量

ts的基础用法

html写法:

{{ a ? 'true' : 'false'}}
?{{ b.map(v=>({num : v})) }}
?{{ message == 0 ? '我是牛马' : '你们是牛马' }}
?{{ c+2 }}
?{{ d.split(',') }}

script写法

const 变量名: 类型 = 初始值

// 模板插值语法
const a:number = 1
const b :number[] =[1,2,3,4,5]
const message:number = 1
const c :number =2
// 操作API 也是支持的
const d:string = '我,爱,学,习'

最终输出的结果会在页面展示出来

数组类型
  1. let list: number[] = [1, 2, 3]

  2. list.push(4) // 可以调用数组上的方法

泛型写法

基础用法:

let arr:Array<boolean> = [true,false]
let arr:number[] = [1,2,3,4,5]

二维数组:

let arr:number[][] = [[1],[2],[3]] (推荐使用)
泛型用法:let arr:Array<Array<number>> = [[1],[2],[3]]

数组里面类型过多:

let arr:any[] = [1,'aaa',true,{}] ?(大杂烩数组)
也可以使用元组:
let arr:[number,string,boolean,{}] = ?[1,'aaa',true,{}]

注意:注意问题:数组定义后,里面的数据的类型必须和定义数组的时候的类型是一致的,否则有错误提示信息,也不会编译通过的

// 数组定义方式2:泛型的写法
// 语法: let 变量名: Array<数据类型>=[值1,值2,值3]
const arr2:Array<number> =[100,200,300]
console.log(arr2);

元组

元组类型:在定义数组的时候,类型和数据的个数一开始就已经限定了

// 元组
let arr3:[string,number,boolean]=['小牛马',100.123456,true]
console.log(arr3);

注意问题:元组类型在使用的时候,数据的类型的位置和数据的个数 应该和在定义元组的时候的数据类型及位置应该是一致的

console.log(arr3[0].split(''));
console.log(arr3[1].toFixed(2)); ? //保留2位

? ? const arr:[x:number,y:boolean]=[1,false]
? ? type first = typeof arr['length']

枚举

enum类型是对 JavaScript 标准数据类型的个补充。 使用枚举类型可以 为一组数值赋予友好的名字 。

// 枚举
//枚举类型,枚举里面的每个数据值都可以叫元素,每个元素都有自己的编号,编号是从0开始的,依次的递增加1
enum Color{
? red,
? green,
? blue
}
// 定义一个color的枚举类型的变量来接收枚举的值
const color:Color = Color.red
console.log(color);
console.log(Color.red,Color.green,Color.blue);
console.log(Color[2]);

默认情况下,从 开始为元素编号。你也可以手动的指定成员的数值

enum Color{
? red=3,
? green=4,
? blue=5
}

一般不这么写

也可以通过给的值来获取到名字

console.log(Color[5]);

字符串枚举

? ? enum Color{
? ? ? ? red='red',
? ? ? ? green='green',
? ? ? ? blue='blue'
? ? }
? ? console.log(Color.red);
? ? console.log(Color.green);
? ? console.log(Color.blue);

使用字符串赋值时,必须全写不写会报错,因为他要进行递增

异构枚举(number和字符串混用)

? ?enum Color{
? ? ? ? yes=1,
? ? ? ? no='no',
? ? }
? ? console.log(Color.yes);
? ? console.log(Color.no);

反向映射

?enum Types{
? ? success = 4
? ?}
? ?let success:number = Types.success
? ?let key = Types[success]
? ?console.log(`value---${success}`,`key---${key}`);

any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。那么我们可以使用 any 类型来标记这些变量:

?let str:any =100
?str ='年少不知富婆好,错把少女当成宝'
?console.log(str);

在对现有代码进行改写的时候, any 类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。并且当你只知道一部分数据的类型时, any 类型也是有用的。比如,你有一个数组,它包含了不同的类型的数据:

// 当一个数组中要存储多个数据,个数不确定,类型不确定,此时也可以使用anv类型来定义数组
let arr4:any[] =[100,'年少不知软饭香,错吧青春到插秧',true]
console.log(arr4);

又优点也有缺点,缺点就是:

当对number类型的数据使用字符串spilt时,编译可以通过,但页面中会报错

any可以调用任何属性和方法,代码中不会给提示了

void

某种程度上来说,void 类型像是与 any 类型相反,它 表示没有任何类型 。 当一个函数没有返回值时,你通常会见到其返回值类型是void

在函数声明的时候,小括号的后面使用:void,代表的是该函数没有任何的返回值

function showMsg():void{
? console.log('只要富婆把握住,连夜搬进大别墅');
}
console.log(showMsg());

只要富婆把握住,连夜搬进大别墅
undefined

也可以定义void类型的变量,可以接收一个undefined的值,但是意义不是很大

unknown

总结:

只能赋值给自身 或者是any

没有办法读任何属性 方法也不可以调用

比 any 更加安全

遇到一些不知道的类型优先使用的是unknown类型,其次在是any类型

never

never类型表示的是那些永不存在的值的类型。

有些情况下值会永不存在,比如,

  • 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值,因为抛出异常会直接中断程序运行。

  • 函数中执行无限循环的代码,使得程序永远无法运行到函数返回值那一步。

Object,object,{}

Object跟原型有关,也就是说原型链的顶端就是Object或者function,也就意味着所有的原始类型和对象类型最终都指向这个Object

在TS中Object表示包含了所有类型,可以等于任何一个值

let a:Object = 123
let a1:Object = '那么'
let a2:Object = {}
let a3:Object = []
let a4:Object = false?

object表示非原始类型也就是除 number, string ,boolean 之外的类型使用 object类型,就可以更好的表示像object.create这样的 API

object 必须是{} 这种类型的数据, any 啥都行

可以理解为object是所有类型的父类

let a4:object = 123 //错误,原始类型
let a5:object = false //错误,原始类型

object一帮常用于泛型的约束,可以把引用类型,函数类型,对象筛选出来,原始类型(string,number,boolean)在object中是无法使用的

//定义一个函数,参数是object类型,返回值也是object类型
function getObj(obj:object):object {
  console.log(obj);
  return{
    name:'卡卡西',
    age:20
  }
  }
console.log(getObj({name:'鸣人',sex:"男"}));

{name:"鸭人,sex: 勇1{name:'卡卡西,age: 20]}

{}空对象,可以理解为new Object,也是可以包含所有的类型

补充:{}虽然可以赋值任意类型,但是赋值之后是不能修改

let a:{} = {name:1,age:18}
a.age = 2

a是无法修改的,尽量少用

函数类型

函数涉及的类型实际上指的是:函数参数返回值的类型,函数没有明确返回值,默认返回 Void 类型

函数返回类型:

function add = (a:number,b:number):number{
?? ?return a + b
}
console.log(add(1+1));

注意: 这里的 | 竖线,在 TS 中叫做联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种。不要和 js 中的 || 搞混

箭头函数的定义:

const add =(a:number,b:number):number => a+b
console.log(add(1,1));?? ??? ?//也可以称函数的赋值

const add3:(x:number,y:number)=>number = function(x:number,y:number):number{
? ? return x +y
}
console.log(add3(100,100));

add3---->变量名--->函数add3

(x:number,y:number)=>number 当前函数的类型

function(x:number,y:number):number{ return x +y } 就相当于符合上面的这个函数类型的值

也可以给函数参数提前赋值

function add = (a:number = 10,b:number= 20):number{
?? ?return a + b
}
console.log(add());

也可以传一个对象

使用interfance来声明(方便),interface 是 TS 设计出来用于定义对象类型的

interfance User{
?? ?name:string
?? ?age:number
}
function add(user:User):User{
?? ?return user
}
console.log(add({name:"小明",age:18}));

对函数(TS)的一个增强(this指向),定义this类型,js中无法使用,必须是第一个参数定义this类型

interfance Obj{
?? ?user:number[]
?? ?add:(this.Obj,num:number)=>void ?//函数的默认返回是Void类型
}
let obj:Obj = {
?? ?user:[1,2,3],
?? ?add(this:Obj,num:number){
?? ??? ?this.user.push(num)
?? ?},
}
obj.add(4);

this:Obj是this定义的类型,使用过程中不算第一个参数,但必须在第一位声明,当this声明以面num声明的类型就相当于第一位参数类型

好处

  1. 给类型起别名

  2. 定义了新类型

类型推论 在 TS 中,某些没有明确指定类型的情况下,TS 的类型推论机制会自动提供类型。好处:由于类型推论的存在,有些情况下的类型注解可以省略不写

function add(num1:number,num2:number,num3?:number):number {
return num1 +num2
}

已知函数的两个参数都是number类型,那么该函数的返回值也是 number 类型。

接口能够描述JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。 为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

通过接口的方式作为函数的类型来使用

// 函数类型:通过接口的方式作为函数的类型来使用

// 定义一个接口,用来作为某个函数的类型使用
interface ISearchFunc{
? // 定义一个调用签名
? (source:string,subString:string):boolean
}
// 定义一个函数,该类型就是上面定义的接口
const seachString :ISearchFunc = function (source:string,subString:string):boolean{
? // 在source字符串中查找substring这个字符串
? return source.search(subString) > -1
}
// 调用函数r
console.log(seachString('哈哈,都是牛马','牛马'));

可选参数()

参数后加个问号,代表这个参数是可选的

严格模式下可选参数会报错

function add(num1:number,num2:number,num3 ?:number):number {
? return num1 +num2
}

注意可选参数要放在函数入参的最后面,不然会导致编译错误。

默认参数

function add2(x:number=100,y:number):number{
? return x + y
}

跟 JS 的写法一样,在入参里定义初始值。

和可选参数不同的是,默认参数可以不放在函数入参的最后面

add2(100)

如果理所当然地觉得 x 有默认值,只传一个就传的是 y 的话,就会报错,编译器会判定你只传了 x,没传 y

如果带默认值的参数不是最后一个参数,用户必须明确的传入 undefined值来获得默认值。

add2(undefind,100)

函数返回类型void

在 ts 中,如果一个函数没有返回值,应该使用 void 类型

function greet(name: string): void { console.log('Hello', name) //}

可以用到void 有以下几种情况

  1. 函数没写return

  2. 只写了 return, 没有具体的返回值

  3. return 的是 undefined

#####

可选和默认值的区别

相同点: 调用函数时,可以少传参数

区别:设置了默认值之后,就是可选的了,不写就会使用默认值; 可选的参数一定有值。

注意:它们不能一起使用。优先使用默认值

对象类型-单独使用

格式: 方法有两种写法: 普通函数 和 箭头函数

函数赋值

在 TS 中函数不能随便赋值,会报错的

// 默认参数
function add2(x:number=100,y:number):number{
? return x + y
}
add2(undefined,100) // 200
// 函数赋值
// const add3:(x: number, y: number) => number = add2
const add3 =add2

不用定义 add3 类型直接赋值也可以,TS 会在变量赋值的过程中,自动推断类型

剩余参数

必要参数,默认参数和可选参数有个共同点: 它们表示某一个参数。有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在JavaScript 里,你可以使用 arguments 来访问所有传入的参数。 在 TypeScript 里,你可以把所有参数收集到一个变量里:剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号 ( ...)后面给定的名字,你可以在函数体内使用这个数组。

剩余参数(rest参数)

剩余参数是放在函数声明的时候所有的参数的最后

function showMsg(str:string,str2:string,...args:string[]){
console.log(str);
console.log(str2);

console.log(args);

}
showMsg('a','b','c','d','e')

...args:string[]剩余的参数,放在了一个字符串的数组中,args里面

函数重载

函数重载是指两个函数名称相同,但是参数个数或参数类型不同,他的好处显而易见,不需要把相似功能的函数拆分成多个函数名称不同的函数。

在引出函数重载之前我们需要了解一个前提知识,在 JS 里同一个函数是可以被声明多次的,结果是会优先采用最后一次的函数体,

回顾:函数基本上是由四部分组成:一个函数名,一个函数参数,一个函数体,一个返回值。

函数重载:

函数名相同,而形参不同的多个函数在JS中,由于弱类型的特点和形参与实参可以不匹配,是没有函数重载这一说的 但在TS中, 与其它面向对象的语言(如Java)就存在此语法

函数重载:函数名字相同,允许在同一范围内指定多个同名函数,函数的参数几个数不同

函数重载概念主要由两部分组成。1.重载签名 2.实现签名

首先声明一个函数

// 函数声明
function add(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 //数字相加
? }
}

// 函数调用
// 两个参数都是字符串
console.log(add('小明','晓龙'));
// 两个参数都是数字
console.log(add(10,20));
// 此时如果传入的是非法的数据
console.log(add('小明',20));
console.log(add(10,'小农'));

输出结果为:

小明晓龙
?30
?undefined
?undefined

此时声明一个函数重载

function add(x:string,y:string):string
function add(x:number,y:number):number

ts应该给我提示出错误的信息内容,报红色错误的信息

console.log(add('小明',20));
console.log(add(10,'小农'));

参数类型不对,直接报错

总结:

1.TS 可以帮你分别约束 参数类型返回值类型,而重载签名的意思就是只需要你提供一个函数的参数类型返回值类型,不需要你提供函数体。并且这个函数的参数类型要完全包含函数签名所有类型

2.函数重载可以有多个重载签名,但是只允许有一个实现签名。说白了就是一个函数名只能有一个函数体

3.函数重载不仅仅只能约束参数类型,还能根据参数的数量去返回不同的类型的返回值

4.Class 类也可以实现 constructor 的重载。

接口

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口 (lnterfaces)来定义对象的 类型。 接口是对象的状态(属性)和行为(方法)的抽象(描述)

接口是对象的状态(属性)和行为(方法)的抽象(描述)

接口:是一种类型,是一种规范,是一种规则,是一个能力

interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

当一个对象类型被多次使用时,有如下两种方式来来描述对象的类型,以达到复用的目的:

  1. 类型别名,type

  2. 接口,interface

interface 接口名 {属性1: 类型1, 属性2: 类型2}

属性必须和类型定义的时候完全一致。

// 接口
// 接口是对象的状态(属性)和行为(方法)的抽象(描述)
// 接口:是一种类型,是一种规范,是一种规则,是一个能力

interface IPerson{
? // id是只读的,是number类型
? readonly id:number
? name:string
? age:number
? // ?可有可无
? sex?:string
}
// 定义一个对象,该对象的类型就是我定义的接口Iperson
const person:IPerson={
? id:1,
? name:'小甜甜',
? age:18,
? sex:'女'
}
console.log(person);

在typescript中,我们定义对象的方式要用关键字interface(接口),我的理解是使用interface来定义一种约束,让数据的结构满足约束的格式

//这样写是会报错的 因为我们在person定义了a,b但是对象里面缺少b属性
//使用接口约束的时候不能多一个属性也不能少一个属性
//必须与接口保持一致
interface Person {
? ? b:string,
? ? a:string
}
?
const person:Person ?= {
? ? a:"213"
}

重名和继承

//重名interface ?可以合并
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}
//继承
interface A{
? ? name:string
}
?
interface B extends A{
? ? age:number
}
?
let obj:B = {
? ? age:18,
? ? name:"string"
}

接口和类型 的区别 interface(接口)type(类型别名)的对比:

相同点:都可以给对象指定类型 不同点: 接口,只能为对象指定类型。它可以继承。 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名 先有的 interface,后有的 type,推荐使用 type

可选属性|只读属性

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在

一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用readonly 来指定只读属性:

readonly vs const 最简单判断该用readonly还是const使用的话用 const,若做为属性则使用的方法是看要把它做为委量使用是做为一个属性。 做为变量readonly 。

interface IPerson{
? // id是只读的,是number类型
? readonly id:number
? name:string
? age:number
? // ?可有可无
? sex?:string
}


const iperson:IPerson ?= {
?? ?id:number
??? ?name:string
? ?? ?age:number
?}

带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ?符号可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。

任意属性(自定义属性) [propName: string]

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

//在这个例子当中我们看到接口中并没有定义C但是并没有报错
//应为我们定义了[propName: string]: any;
//允许添加新的任意属性
interface Person {
? ? b?:string,
? ? a:string,
? ? [propName: string]: any;
}
?
const person:Person ?= {
? ? a:"213",
? ? c:"123"
}

duck typing(鸭子类型)

看到这里,你会发现,interface 的写法非常灵活,它不是教条主义。

用 interface 可以创造一系列自定义的类型。

事实上, interface 还有一个响亮的名称:duck typing(鸭子类型)。

(四不像)

对于传统的 JavaScript 程序我们会使用 函数基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员使用这些语法就有些棘手,因为他们用的是 基于类的继承 并且对象是由类构建出来的。从ECMAScript 2015,也就是 ES6 开始,JavaScript 程序员将能够使用基于类的面向对象的方式。使用 TypeScript,我们允许开发者现在就使用这些特性,并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行,而不需要等到下个 JavaScript 版本。

类可以理解为模板,通过模板可以实例化对象

class Person4{
? ? // 定义属性
? ? name:string
? ? age:number
? ? sex:string
? ? // 定义构造函数:为了将来实例化对象的时候,可以直接对属性的值进行初始化
? ? constructor(name:string='小朋友',age:number=16,sex:string='女'){
? ? ? // 更新对象中的属性数据
? ? ? this.name = name
? ? ? this.age = age
? ? ? this.sex = sex
? ? }
? ? // 定义实例方法
? ? sayHi(str:string){
? ? ? console.log(`大家好,我是${this.name},今年已经${this.age}岁了,是个${this.sex}孩子`,str);
? ? }
? }
? //ts中使用类,实例化对象,可以直接进行初始化操作
? const person4 = new Person4('佐助',18,'男')
? person4.sayHi('你叫什么名字')

类类型

类实现接口 与C# 或Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。

(1)类和接口的实现

类 类型:类的类型,类的类型可以通过接口来实现

定义一个接口

interface IFly{
? // 该方法没有任何的实现(方法中上面都没有)
? fly()
}

定义一个类,这个类就是上面定义的接口(实际上也可以理解为,IFly接口约束了当前的这个Person类)

class Person implements IFly{
? // 实现接口中的方法
? fly(){
? ? console.log('我飞的更远');
? }
}

实例化对象

const person = new Person()
person.fly()

定义一个接口,发现当前这个类可以实现多个接口,一个类同时也可以被多个接口进行约束:

// 定义一个接口
interface ISwim{
? swim()
}
//定义一个类,这个类的类型就是IFly和ISwim(当前这个类可以实现多个接口,一个类同时也可以被多个接口进行约束)
class Person2 implements IFly,ISwim{
? fly() {
? ? ? console.log('我飞的更快');
? ? ? }
? ? ? swim() {
? ? ? ? ? console.log('我会游泳');
? ? ? }
}
// 实例对象
const person2 =new Person2()
person2.fly()
person2.swim()

总结:

类可以通过接口的方式,来定义当前这个类的类型

类可以实现一个接口,类也可以实现多个接口,要注意,接口中的内容都要真正的实现

(2)类和接口的继承

定义了一个接口,继承其他多个接口

// 定义了一个接口,继承其他的多个接口
interface IMyFlyAndSwim ?extends IFly,ISwim{}
? // 定义了一个类,直接实现IMyFlyAndSwim这个接口
? class Person3 implements IMyFlyAndSwim{
? ? fly() {
? ? ? console.log('我飞的更快');
? ? ? }
? ? ? swim() {
? ? ? ? ? console.log('我会游泳');
? ? ? ? ??
? ? ? }
? }
? const person3 =new Person3()
? person3.fly()
? person3.swim()

总结:接口和接口之间叫继承(使用的是extends关键字),类和接口之间叫实现(使用的是implements)

接口继承

如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用 语法:

在 TypeScript 里,我们可以使用常用的面向对象模式。基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。

继承:类与类之间的关系

继承后类与类之间的叫法:A类继承了B类,那么此时A类叫子类,B类叫基类

子类--->派生类

基类--->超类(父类)

一旦发生继承的关系,就出现了父子类的关系(叫法)

先定义一个父类:

class Person{
? // 定义属性
? name:string?
? age:number
?sex:string
? // 定义构造函数
? constructor(name:string,age:number,sex:string){
? ? // 更新属性数据
? ? this.name =name
? ? this.age = age
? ? this.sex = sex
? }
? // 定义实例方法
? sayHi(str:string){
? ? console.log(`我是:${this.name},${str}`);
? ??
? }
}

在定义一个子类

// 定义一个子类
class Student extends Person{
? constructor(name:string,age:number,sex:string){
? ? // 调用父类中的构造函数,使用的是super
? ? super(name,age,sex)
? }

? // 可以调用父类中的方法
? sayHi(){
? ? console.log('我是学生类中的sayHi方法');
? ? // 调用父类中的sayHi方法
? ? super.sayHi('')


? }
}

最后实例化方法

// 实例化Person
const person = new Person('牛马',18,'男')
person.sayHi('牛逼')

// 实例化Student
const stu = new Student('小米',20,'女')
stu.sayHi()

总结:类和类之间如果有继承关系,需使用extends关键字

子类中可以使用父类中的构造函数,使用的关键字是super(包括调用父类中的实例方法,也可以使用super)

子类中可以重写父类中的方法

多态

多态:父类型的引用指向了子类型的对象,不同类型的对象针对相同的方法,产生了不同的行为

ts中没有向下转型

// 定义一个父类
class Animal{
? ? // 定义一个属性
? ? name:string
? ? // 定义一个构造函数
? ? constructor(name:string){
? ? ? ? // 更新属性值
? ? ? ? this.name = name
? ? }
? ? // 实例方法
? ? run(distance:number=0){
? ? ? ? console.log(`跑了${distance}米这么远的距离`);
? ? ? ??
? ? }
}
// 定义一个子类
class Dog extends Animal{
? ? // 构造函数
? ? constructor(name:string){
? ? ? ? // 调用父类的构造函数,实现子类中属性的初始化操作
? ? ? ? super(name)
? ? }
? ? // 实例化方法,重写父类中的实例方法
? ? run(distance:number=5){
? ? ? ? console.log(`跑了${distance}米这么远的距离`);
? ? ? ??
? ? }

}
// 定义一个子类
class Pig extends Animal{
? ? // 构造函数
? ? constructor(name:string){
? ? ? ? // 调用父类的构造函数,实现子类中属性的初始化操作
? ? ? ? super(name)
? ? }
? ? // 实例化方法,重写父类中的实例方法
? ? run(distance:number=10){
? ? ? ? console.log(`跑了${distance}米这么远的距离`);
? ? ? ??
? ? }

}
// 实例化父类对象
const ani:Animal = new Animal('动物')
ani.run()
// 实例化子类对象
const dog:Animal = new Dog('大黄')
dog.run()
// 实例化子类对象
const pig:Animal = new Pig('小猪')
pig.run()

通过观察上面的代码,发现父类和子类的关系:父子关系,此时,父类类型创建子类的对象

const dog1:Animal = new Dog('小黄')
dog1.run()
const pig1:Animal = new Pig('小猪')
pig1.run()

dog1pig1类型就相当于是Animal,也就是说明她两给都是属于父级类型,说明父类型的引用指向了子类型的对象

以上代码也可以实现效果

用方法来写的话

// 该函数需要的参数是Animal类型的
function showRun(ani:Animal){
? ? ani.run()
}

showRun(dog1)
showRun(pig1)

实际上,dog1和pig1都是属于Animal,而且Animal中是存在run方法的,子类看着都是Aniaml类型,但实际上都是子类型,并且他们调用的方法都是各自的方法,说明不同类型的对象针对相同的方法,产生了不同的行为

public

在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 public 来做修饰;例如,C# 要求必须明确地使用 pubic 指定成员是可见的。在 TypeScript 里,成员都默认为 public 你也可以明确的将一个成员标记成 public 。 我们可以用下面的方式来重写上面的 Animal 类:

类中的成员都有自己的访问修饰符,public

public修饰符,类中成员默认的修饰符,代表的是公共的,任何位置都可以访问类中的成员----------公共

// 定义一个类
class Person{
? ? // 属性 public修饰了属性成员
? ?public ?name:string
? ? // 构造函数
? ? public ?constructor(name:string){
? ? ? ? // 更新属性
? ? ? this.name = name
? ? }
? ? // 方法
? ? public eat(){
? ? ? ? console.log('嗯这个骨头真好处',this.name);
? ? }
}
// 实例化对象
const per = new Person('大蛇丸')
// 类的外部可以访问类的属性成员
console.log(per.name);
per.eat()

发现修饰符public确实可以正常访问

private

当成员被标记成 private 时,它就不能在声明它的类的外部访问**(private修饰符,类中的成员如果使用private来修饰,那么外部是无法访问这个成员数据的,当然,子类中也是无法访问该成员数据的)

private name:string

? ? // 属性 ? 修饰了属性成员
? ? //public ?name:string
? ?// 属性 private 修饰了属性成员
? ? //private name: string
? ? // 属性protected 修饰了属性成员
? ? protected name:string
? ? // 构造函数
? ? public ?constructor(name:string){
? ? // 更新属性
? ? ? this.name = name
? ? }
? ? // 方法
? ? public eat(){
? ? ? ? console.log('嗯这个骨头真好处',this.name);
? ? ? ??
? ? }
}

说明子类中也是无法访问该成员数据的

protected

protected 修饰符与 private 修饰符的行为很相似,但有一点不同,protected 成员在派生类中仍然可以访问

protected修饰符,类中的成员如果使用protected来修饰,那么外部是无法访问这个成员数据的,当然,子类中是可以访问该成员数据的

? protected name:string

// 定义一个子类
class Student extends Person{
?? ?constructor(name:string){
?? ??? ?//构造函数super(name)
?? ??? ?}
?? ?play(){
?? ?console.log(我就喜欢玩游戏');
?? ?}
// 实例化对象
const per = new Person('大蛇丸')类的外部可以访问类的属性成员
per.eat()
console.log(per.name); 属性“name”受保护,只能在类“person”及其子类中访问。(这段报错了)

发现外部是无法访问这个成员数据的,修改成内部就可以了

// 实例化对象
const per = new Person(大蛇丸)
//类的外部可以访问类的属性成员//?
console.log(per.name);
per.eat()
const stu = new Student('红豆')
stu.play()

readonly

你可以使用 readonly 关键字将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。

readonlv修饰符:首先是一个关键字,对类中的属性成员进行修饰,修饰符后,该属性成员,就不能在外部被随意的修改

构造函数中的参数可以使用readonly进行修饰,一旦修饰了,那么该类中就有了这个只读的成员属性了,外部可以访问,但是不能修改

先定义一个类型:

// 定义一个类型
class Person{
? ? // 属性
? ? name:string
? ? constructor(name:string){
? ? ? ? this.name = name
? ? }
? ? sayHi(){
? ? ? ? console.log('小牛马',this.name);
? ? ? ??
? ? }
}
// 实例化对象
const person: Person = new Person('大牛马')
console.log(person);
console.log(person.name);
person.name = '老牛马'
console.log(person.name);

对name属性添加修饰符

?readonly ?name:string

发现实例化的对象中的name报错了,无法为“name”赋值,因为它是只读属性

//此时无法修改,因为name属性是只读的
person.name = '老牛马'
//无法为“name”赋值,因为它是只读属性

发现类中的普通方法中,也是不能修改readonly修饰的成员属性值,

?sayHi(){
? ? ? ? console.log('小牛马',this.name);
? ? ? ? //类中的普通方法中,也是不能修改readonly修饰的成员属性值
? ? ? ? this.name = '啦啦啦'
? ? }

构造函数中,可以对只读的属性成员的数据进行修改

如果构造函数中没有任何的参数,类中的属性成员此时已经使用readonly进行修饰了,那么外部也是不能对这个属性值进行更改的

? ?// 属性
? ?readonly ?name:string='啦啦啦啦啦'
? ? constructor(name:string){
? ? ? ? this.name = name
? ? }

构造函数中写上参数()就可以进行更改,实例化就可以调用:

const person: Person = new Person('大牛马')

现在访问得就是“大牛马”

以上就是对 readonly修饰类中的成员属性操作:

以下是 readonly修饰类中的构造函数中的参数(参数属性)

构造函数中的name参数,一旦使用readonly进行修饰后,

1.那么该name参数可以叫参数属性用

2.那么Person中就有了一个name的属性成员

3.外部也是无法修改类中的name属性成员值的

(1)构造函数中的name参数,一旦使用public进行修饰后,那么Person中就有了个公共的name屈性成员了

? ?constructor(public name:string = '张三'){
? ? ? ? this.name = name
? ? }

(2)构造函数中的name参数,一旦使用private进行修饰后,那么Person类中就有了一个私有的name属性成员了

constructor(private name:string = '张三'){
? ? ? ? this.name = name
? ? }

(3) 构造函数中的name参数,一旦使用protected进行修饰后,那么Person类中就有了个受保护的name属性成员(只能在本类和派生类中访问及使用)

constructor(protected name:string = '张三'){
? ? ? ? this.name = name
? ? }

4)构造函数中的参数可以使用public及privte和protected进行修饰,无论是哪个进行修饰,该类中都会自动的添加这么一个属性成员

存取器

TypeScript 支持通过 getters/setters 来截取对对象成员的访问。它能帮助你有效的控制对对象成员的访问。

外部可以传入姓氏和名字数据,同时使用set和get控制姓名的数据,外部也可以进行修改操作

例:get

class Person{
? ? firstName:string //姓氏
? ? lastName:string ?//名字
? ? constructor(firstName:string,lastName:string){
? ? ? ? this.firstName = firstName
? ? ? ? this.lastName = lastName
? ? }
? ? // 姓名的成员属性(外部可以访问,也可以修改)

? ? // 读取器------负责读取数据的
? ? // 姓名====>姓氏和名字的拼接
? ? get fullName(){
? ? ? ? return this.firstName+ '_' +this.lastName
? ? }
}
// 实例化对象
const person:Person = new Person('东方','不败')
console.log(person);
// 获取该属性成员
console.log(person.fullName);

如果只有set只能设置,只有get只能读取

下面是通过set和get来修改后的代码:

class Person{
? ? firstName:string //姓氏
? ? lastName:string ?//名字
? ? constructor(firstName:string,lastName:string){
? ? ? ? this.firstName = firstName
? ? ? ? this.lastName = lastName
? ? }
? ? // 姓名的成员属性(外部可以访问,也可以修改)

? ? // 读取器------负责读取数据的
? ? // 姓名====>姓氏和名字的拼接
? ? get fullName(){
? ? ? ? console.log('get中.....');
? ? ? ? return this.firstName+ '_' +this.lastName
? ? }
? ? // 设置器----负责设置数据的修改
? ? set fullName(val){
? ? ? ? console.log('set中.....');
? ? ? ? // 姓名---->把姓氏和名字获取到重新的赋值给firstName和lastName
? ? ? ? let names = val.split('_')
? ? ? ? this.firstName = names[0]
? ? ? ? this.lastName = names[1]
? ? }
}

// 实例化对象
const person:Person = new Person('东方','不败')
console.log(person);
// 获取该属性成员
console.log(person.fullName);
// 设置该属性的数据
person.fullName = '诸葛_孔明'
console.log(person.fullName);

输出结果

Person {firstName:东方,LastName: 不败}
get中....
东方_不败
set中.....
get中....
诸葛_孔明

静态属性

到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。在这个例子里,我们使用都要static 定义 origin ,因为它是所有网格都会用到的属性。每个实例想要访问这个属性的时候在 origin 前面加上类名。如同在实例属性上使用 this.xxx 来访问属性一样,这里我们使用Grid.xxx 来访问静态属性

静态成员是指,附着在类上的成员(属于某个构造函数的成员),在ts 中的表现是在类中使用 static修饰符进行修饰的成员。

?// 静态属性
? ?static name:string = "喜羊羊"
? ? // 静态方法
? ?static sayHi(){
? ? ? ? console.log('牛马');
? ? ? ??
? ? }

构造函数是不能通过static来进行修饰的

// 通过类名.静态属性的方式来访问该成员数据
console.log(Person.name);
// 通过类名.静态属性的方式来设置该成员数据
Person.sayHi();

抽象类

抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。

(抽象类:包含抽象方法(抽象方法一般没有任何的具体内容的实现),也可以包含实例方法,抽象类是不能被实例化,为了让子举讲行实例化及实现内部的抽象方法)

抽象方法一般没有任何的具体内容的实现,都只是描述,例如:

abstract eat(){
console.log('真好吃');
//报错了,抽象方法不能有具体的体现

并且

//不能实例化抽象类的对象
const ani:Animal = new Animal()

抽象类的目的或者是作用最终都是为子类服务的

? ? abstract class Vue {
? ? ? ? name:string
? ? ? ? constructor(name?:string) {
? ? ? ? ? ? this.name = name;
? ? ? ? }
? ? ? ? getName():string{
? ? ? ? ? ? return this.name
? ? ? ? }
? ? ? ? abstract init(name:string):void
? ? }
? ? //如果不写 abstract init(name:string):void这句代码,getName方法就可以实现,加上的话就是就只能进行一个描述

再写一个派生类对init进行一个实现

? class React extends Vue{
? ? ? ? constructor(){
? ? ? ? ? ? super()
? ? ? ? }
? ? ? ? init(name: string){

? ? ? ? }
? ? ? ? setName(name:string){
? ? ? ? ? ? this.name = name;
? ? ? ? }
? ? }
? ? const react = new React()
? ? react.setName('马牛马')
? ? console.log(react.getName());

规范了继承他的子类必须拥有抽象属性与抽象方法

提供了抽象属性与抽象方法来让继承他的子类重写,来实现不同的处理方式

他的保护属性与方法可以被每个子类继承来实现继承子类的公有部分,这部分方法属性处理的是相同的任务

它类似于一个工厂,每个子类都可以去他那里继承公有的部分,但是也必须拥有它私有的部分,每个继承的子类都有相似的部分,又有他们独特的部分

this类型

class StudyStep {
? step1() {
? ? console.log('listen')
? ? return this
? }
? step2() {
? ? console.log('write')
? ? return this
? }
}
?
const s = new StudyStep()
?
s.step1().step2() ? ?// 链式调用

interface 和 class 的关系

interface 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。

interface 同样可以用来约束 class,要实现约束,需要用到 implements 关键字。

implements

implements 是实现的意思,class 实现 interface。

interface MusicInterface {
? ? playMusic(): void
}
?
class Cellphone implements MusicInterface {
? ? playMusic() {}
}
复制代码

处理公共的属性和方法

不同的类有一些共同的属性和方法,使用继承很难完成。

比如汽车(Car 类)也有播放音乐的功能,你可以这么做:

  • 用 Car 类继承 Cellphone 类

  • 找一个 Car 类和 Cellphone 类的父类,父类有播放音乐的方法,他们俩继承这个父类

很显然这两种方法都不合常理。

实际上,使用 implements,问题就会迎刃而解。

interface MusicInterface {
? ? playMusic(): void
}
?
class Car implements MusicInterface {
? ? playMusic() {}
}
?
class Cellphone implements MusicInterface {
? ? playMusic() {}
}

这样 Car 类和 Cellphone 类都约束了播放音乐的功能。

再比如,手机还有打电话的功能,就可以这么做,Cellphone 类 implements 两个 interface。

interface MusicInterface {
? ? playMusic(): void
}
?
interface CallInterface {
? ? makePhoneCall(): void
}
?
class Cellphone implements MusicInterface, CallInterface {
? ? playMusic() {}
? ? makePhoneCall() {}
}

泛型

指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。

泛型:在定义函数、接口、类的时候不能预先确定要使用的数据的类型,而是在使用函数、接口、类的时候才能确定数据的类型

首先先定义一个普通的函数,使用数组来接受值

function getArr(value:number,count:number):number[]{
? // 根据数据和数量产生一个数组
? const arr : number[] = []
? for(let i = 0;i< count;i++){
? ? arr.push(value)
? }
? return arr
}
const arr1 = getArr(100.2333,6);
console.log(arr1);

输出的结果为,这里第一个参数value是接收到值,第二个参数count是数量

(6) [108.2333,180.2333,188.2333, 108.2333, 108.2333, 180.23337?
0: 108.2333
1: 108.2333
2: 108.2333
3: 108.2333
4: 188.2333
5: 108.2333
length: 6

在定义一个函数,同上,只不过传入的是字符串类型,上面是传入的number类型

function getArr2(value:string,count:number):string[]{
? // 根据数据和数量产生一个数组
? const arr : string[] = []
? for(let i = 0;i< count;i++){
? ? arr.push(value)
? }
? return arr
}
const arr2 = getArr2('abc',3);
console.log(arr2);

输出的结果为:

(3) [ 'abc',"abc', 'abc']?
0:"abc"
1: "abc"
2: "abc"
length: 3

类型为any也可以

那么泛型是怎样写的呢,就比如下面:

function getArr4<T>(value:T,count:number):T[]{
? const arr:Array<T>=[]
? ? for(let i = 0;i < count; i++){
? ? ? arr.push(value)
? ? }
? ? return arr
}
const arr1 = getArr4<number>(200.12345,5)
const arr2 = getArr4<string>('abc',3)
console.log(arr1);
console.log(arr2);

还有更简单的方法

//动态类型
function xiao<T>(a:T,b:T):Array<t>{
?? ?return [a,b]
}
xiao(1,2,) //
xiao('1','2')
xiao(false,true)

类型别名:

type A<T> = string | number | T
let a:A<null> = null

接口

interface Data<T> {
?? ?msg:T
}
let data:Data<number> = {
?? ?msg:1
}

总结:

1.这就是用一个<T>来接受参数的类型,就是相当于把类型当作形参,然后在参数前在声明参数的类型,让<T>来接受

2.泛型<T> T类型变量,表示任何类型。帮助我们捕获用户传入的类型(比如:number)

3.T代表的是一种类型,也就是不能同时传string或者number,T只能代表一种东西,但是any可以,区别就是这个有类型限制,限制是同一类型,并且用泛形,传入的值会有类型的提示,语法提示,错误提示。。。。用any

多个泛型参数的函数

一个函数可以定义多个泛型参数

多个泛型参数的函数:函数中有多个泛型的参数

例如:

? ? function getMsg<K,V>(value1:K,value2:V):[K,V]{
? ? ? ? return [value1,value2]
? ? }
? ? const arr = getMsg<string,number>('jack',1000.123)
? ? console.log(arr[0].split(''),arr[1].toFixed(1));

这就是相当于有两个不同类型的参数,我也需要去定义多个泛型来接受

?function getMsg<K = number,V = number>(value1:K,value2:V):[K,V]{
? ? ? ? return [value1,value2]
? ? }
? ?getMsg(false,'1')

也可以给K和V去给一个默认值,刚开始不知道什么类型,最后输出的参数就是值的类型(动态类型)

泛型约束

因为泛型灵活,所以用泛型,但是太灵活会导致报错,所以用约束

关键字:extends,keyof

定义一个泛型

function add<T>(a:T,b:T){
?? ?return a + b
}
add(undefind,undefind)

这里虽说我们使用的是泛型,但是正是因为泛型太灵活了,undefind之间是不能相加的,这是我们可以对其使用泛型约束

function add<T extends number>(a:T,b:T){
?? ?return a + b
}
add(1,2)

使用:在类型后面跟一个extends 在跟一个约束的类型

获取参数长度

//首先定义一个函数
function fn<T>(a:T){
? a.length
}

//接口约束
interface Len {
? length:number
}
function fn<T extends Len>(a:T){
? a.length
}
fn('111')
fn([1,2,3])

对象约束

let obj = {
?? ?name:"牛马",
?? ?sex:"男"
}
//type Key = typeof obj ?//对象的类型
type Key = keyof typeof obj ?//使用keyof可以把对象类型推算成联合类型,方便我们进行约束
?? ??? ??? ??? ??? ??? ??? ?//搭配extends和keyof就可以约束对象上的类型
//定义一个函数,T的我传入的是引用类型,K的我传入对象上的参数
function ob<T extends object,K extends keyof T>(obj:T,key:K){
?? ?return obj[key]
}
//这边就可以自动获取到对象中的参数
ob(obj,"name")

第一个参数T传入的是一个引用类型,第二个参数传入的是对象中的一个key,首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型

高级使用:

让Options成为一个泛型,并且让他接收引用类型,设置为可读属性

//定义一个接口
interface Data{
?? ?name:string
?? ?age:number
?? ?sex:string
}
type Options<T extends object> = {
?? ?//[key in keyof T]遍历循环
?? ?
?? ?readonly [key in keyof T]:T[key] ?//设置为可读属性,key名字随便起
? // ?[key in keyof T]?:T[key]?
?? ?//:T[key] ?其中T代表的是Data,key代表的是T中的数据
}
type B = Options<Data>

js中的遍历:for in for(let key in obj)

上述:[key in keyof T]就是TS中的遍历循环

readonly [key in keyof T] : T [key] 其中T代表的是Data(object)

操作符keyof 可以用于获取某种类型的所有键,其返回类型是联合类型

泛型类

泛型类是指在定义类时使用泛型的类,泛型类可以用于创建可重用的组件,以及在编译时进行类型检查

我们只需要在类名后面,使用 <T, ...> 的语法定义任意多个类型变量

泛型类可确保在整个类中一致地使用指定的数据类型,当函数、接口或类在多个地方使用该数据类型时。

高级类型
联合类型

当我们的参数要传入不同的类型时调用

联合类型(Union Types) 表示取值可以为多种类型中的一种,即由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种

需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值

function getstring(str:numberstring):string(return str.tostring()
console.log(getstring( '123'));

强转方式:

let fun = function (type:number | boolean):boolean{
?? ?return !!type
}
let result = fun(false)
console.log(result)

相当于把number强制转为boolean类型

类型断言

当我们要出入一个数据时,不知道是什么类型是使用

类型断言:告诉编译器,我知道我自己是什么类型,也知道自己在干什么

类型断言的语法方式1:<类型>变量名

类型断言的语法方式2: 值 as 类型

function getString(str:number | string):number{``
? // 如果str本身就是string类型,那麽是没有必要调用tostring类型的
? if((<string>str).length){
? ? // str.length存在吗,如果存在说明string是string类型的
? ? return (str as string).length
? }else{
? ? // 此时说明str时number类型
? ? return str.toString().length
? }
}
console.log(getString('123456789'));
console.log(getString(123));

类型断言不能滥用

const fn = (type:any):boolean=>{
?? ?return type as boolean
}
let bbb = fn(1)
console.log(bbb)

类型推断

我们定义的变量一开始没有定义时,使用

类型推断:TS会在没有明确的指定类型的时候推测出一个类型有下面2种情况:

1.定义变量时赋值了,推断为对应的类型

2.定义变量时没有赋值,推断为any类型

// 类型断言:告诉编译器,我知道我自己是什么类型,也知道自己在干什么
//类型断言的语法方式1:<类型>变量名
//类型断言的语法方式2: 值 as 类型

function getString(str:number | string):number{``
? // 如果str本身就是string类型,那麽是没有必要调用tostring类型的
? if((<string>str).length){
? ? // str.length存在吗,如果存在说明string是string类型的
? ? return (str as string).length
? }else{
? ? // 此时说明str时number类型
? ? return str.toString().length
? }
}
console.log(getString('123456789'));
console.log(etString(123));

类型别名

type,可以描述 对象、函数,都可以扩展

TS中 Interface 与 Type 的区别

接口可以 extends 和 implements ,type则不可以(type可以通过交叉类型实现 extends 行为)
接口遇到重名市声明合并,type则不可以
type 可以声明基本类型别名,联合类型,元组等类型
- type 可以定义字符串字面量联合类型
- type 可以定义声明联合类型
- type 可以定义元组每个位置的类型
Interface: 通过interface声明的接口,或者说声明的变量,它在声明的那一刻并不是最终类型,由于interface可以进行声明合并,所以它在创建后是可变的,可以将新成员添加到同一个interface中
Type: 类型别名不会创建类型,它只是创建了一个新名字来引用那个类型,其实就是一个对象字面量类型,所以类型别名一旦声明就无法更改它们

type的高级语法

type num = 1 extends never ? 1:0

左边的值 会作为右边类型的子类型

unknow和any是最顶级的类型,下面是类型等级

交叉类型:

可以同时写多个,相当于把所有的类型中的数据全部写到一个里面

interfance Pople{
?? ?name:string,
?? ?age:number
}
interfance Man{
?? ?sex:number
}
const xiaoman =(man:Pople & Man):void = >{
?? ?console.log(man);
}
xiaoman({
?? ?name:'小明去上班了',
?? ?age:18,
?? ?sex:2
})

有什么问题,欢迎大家来私信

文章来源:https://blog.csdn.net/Mangcun_tutu/article/details/135679581
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。