TypeScript
TypeScript(简称TS)是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统
往期文章:
TypeScript基础内容(一): 数据类型
下面开始正文
Class
ES6新推出Class,这个东西倒不算陌生,之前做Android一直在用,不过ES6跟这个还是有很大区别
1、基本方法
基本方法包含:静态属性,静态方法,成员属性,成员方法,构造器(构造函数?)、get/set方法
值得注意的一点是,成员属性如果没有赋予默认值,并且不使用是会报错的,如果不想报错就需要添加! ,例如
class Info{
name:string // error ·Property 'name' has no initializer and is not definitely assigned in the constructor.
//如上,想要解决报错,我们只需要加上 !即可
name!:string //OK
}
下面是包含所有基本方法的演示代码
class Info{
static city:string = 'YC' //静态属性
name!:string //成员属性
Info(name: string){
console.log('这里是构造函数')
this.name = name
}
static = () =>{
return '这里是静态方法'
}
CName = () =>{
return '这里是成员方法'
}
get mName(){
console.log('这里是get方法')
return this.name
}
set mName(name) {
console.log('这里是set方法')
this.name = name
}
}
2、私有字段
TS3.8之后提供的新的私有字段
1、 使用#开头,不能在类之外访问(包括子类)
2、 不能使用
public,private,protected修饰符来修饰私有字段,不然就会提示修饰符和私有字段不能一起使用(An accessibility modifier cannot be used with a private identifier)
3、 与
private不同,private只是在编译阶段表明字段私有,哪怕在TS代码中明确声明某一个字段是private,但是一旦编译运行之后就会发现,被标注private的字段仍然能够正常访问
从上面我们可以看出来,运行编译过后的代码,仍然能够正常获取到被标注private的字段属性,
private有名无实。
而#私有字段却能更好的确保每一个私有字段的相互隔离。编译运行之后,获取私有字段属性只能得到undefined
3、只读字段ReadOnly
使用readonly修饰,并且只能够在构造函数中赋值,在编译阶段进行检查
在TS中,只允许将interface,class,type的属性描述为readonly
4、Extends
5、修饰符
public: 公共,当前类、子类、外部类都可以访问
private: 私有,只在当前类访问, 子类和外部类都不允许访问
protected: 保护,当前类、子类可以访问,外部类不允许访问
class Info{
public gender:string='男'
Info(name: string){
console.log('这里是构造函数')
}
}
public: 不用细说,当前类子类外部类均可以访问
private: 同一套代码,gender修饰符修改为private
protected: 同一套代码,gender修饰符修改为protected
6、abstract抽象
抽象类:使用abstract修饰class。不能被实例化,只能实例化实现了抽象类的子类
抽象方法:使用abstract修饰方法,不包含具体实现,需要子类去实现抽象方法
下面使用代码说明abstract的具体使用
abstract class Phone{
constructor(deviceName:string){}
abstract setDeviceId(id:string):void;
}
首先定义一个抽象类,内含一个构造方法以及一个抽象方法供子类实现;然后再定一个子类去实现抽象类,并且实现抽象方法,整体比较简单。
class HUAWEI extends Phone{
deviceId!:string
constructor(deviceName: string){
super(deviceName)
}
setDeviceId(id:string){
this.deviceId = id
console.log(this.deviceId)
}
}
然后我们可以尝试实例化一下抽象类,提示不能创建一个抽象类的instance
接下来,我们实例化抽象类的子类HUAWEI
// let phone = new Phone('phone') //Error
let nova5 = new HUAWEI('HUAWEI-nova5 Pro')
nova5.setDeviceId('0x0f0f010')
创建完成编译这段代码可以得到打印结果,结果符合预期
7、重写和重载
重写: 子类实现父类的方法并且进行实现自己的逻辑
重载: 同一个函数同时拥有多个数据类型定义
1、重写
class Computer{
deviceName:string = 'Computer'
getName(){
console.log(this.deviceName)
}
}
new Computer().getName() //Computer
class Laptop extends Computer{
getName(){
this.deviceName = 'Laptop'
console.log(this.deviceName)
}
}
new Laptop().getName() //Laptop
//可以得到结果
//[LOG]: "Computer"
//[LOG]: "Laptop"
2、重载
class Computer{
deviceName: string= 'computer';
deviceId:number = 0x001
setDeviceOrId(device:string):void; //1
setDeviceOrId(device: number):void; //2
setDeviceOrId(device: string|number){ //3
if(typeof device === 'string'){
this.deviceName = `设备名称: ${device}`
}else{
this.deviceId = device
}
}
getDevice(){
return this.deviceName + ` ; ID: ${this.deviceId}`
}
}
let computer = new Computer()
computer.setDeviceOrId('Laptop')
computer.setDeviceOrId(0x002)
console.log(computer.getDevice())
//打印结果:[LOG]: "设备名称: Laptop ; ID: 2"
看着挺有用的,但是实际上这段代码有点奇怪,TS的重载不像是之前写Java时候的重载
断言
TS断言分为三类 类型断言、非空断言、确定赋值断言,在断言失败后,还会有一个双重断言
断言使用起来比较方便,可以帮我们减少代码以及一些报错,但是如果滥用的话,可能得到的错误以及问题会让你头皮发麻
0、类型推断
TS会根据上下文自动帮我们推断出变量类型,比如说赋予了初始值的变量、给定了默认值的参数以及函数的返回类型,不需要我们在手动显示的定义数据类型,使用起来还是比较方便的
let num = 1
let str = 'string'
let bool = false
console.log(typeof num) //number
console.log(typeof str) //string
console.log(typeof bool) //boolean
//没有赋予初始值,会被TS推断为any类型。建议不要这么写,会跳过TS的类型检查,在某些情况下可能会导致报错
let anyField //推断为any类型
1、类型断言
在某些情况下,我们会明确的知道某一个字段是什么类型,从而不需要TS帮我们进行检查,又或者是有些时候TS帮我们检查之后不准确,出现一些不必要的报错。
先看下面这段代码
//TypeScript
const laptop = {}
laptop.deviceName = 'HUAWEI' //Property 'deviceName' does not exist on type '{}'.(2339)
laptop.deviceID = 0 //Property 'deviceID' does not exist on type '{}'.(2339)
//JavaScript
let laptop = {}
laptop.deviceName = "HUAWEI"
laptop.deviceID = 0
//打印结果
console.log(laptop)
VM261:4 {deviceName: 'HUAWEI', deviceID: 0}
我们知道在laptop中包含两个属性deviceName和deviceID,只是在初始的时候并没有定义。
这样的代码在JS中可以正常运行,完全没有任何问题,只是在TS中会有些问题。
以TS的类型推断逻辑来看,taptop的类型应该是{}, 里面不存在任何属性,我们调用laptop.deviceName就会报错
这个时候我们就可以使用类型断言,直接告诉TS我们知道laptop的类型,你不需要帮我们进行推断了
//先定义好类型
interface Computer{
deviceName: string,
deviceID: number
}
const laptop = {} as Computer //直接进行类型断言告诉TS这个是一个Computer类型
laptop.deviceName = 'HUAWEI'
laptop.deviceID = 0
console.log(laptop)
// 打印结果
// [LOG]: { "deviceName": "HUAWEI", "deviceID": 0 }
类型断言包含两种方式,as和<>,as就是上面使用过的 {} as Computer,至于<>可以看看下面的代码
let strs = 'this is a string'
let strLength:number = (<string> strs).length
console.log(strLength)
//打印结果 [LOG]: 16
2、非空断言
使用!关键字来断言当前操作对象是 非null和undefined类,!会帮助我们从对象中剔除null和undefined
let num: null | undefined | number
//下面这行代码会报错,//Object is possibly 'null' or 'undefined'.(2533)
let str = num.toString()
//这行代码使用非空赋值,排除掉null和undefined
let strs = num!.toString()
上面的例子中,编译器会默认帮我们排除null、undefined类型,只保留下number类型。
但是变成ES5后 !会被移除,所以当传入 null 的时候,还是会打出 null
3、确定赋值断言
在TS 2.7版本中引入了确定赋值断言,允许在实例属性和变量声明后面放置一个 ! 号,以告诉TS该属性会被明确赋值。
TS 并不具备运行时检测,或者仅具备有限的类型接检测能力,这就导致了num没有被检测出来被赋值,然后我们打印num就会提示它在被赋值前使用
解决这个问题就可以使用确定赋值断言,明确告诉TS,我们会给num赋值
let num!: number
initNum()
//在没有给 num加上确定赋值断言之前,下面这行代码会报错,//Error: Variable 'num' is used before being assigned.(2454)
console.log(num)
function initNum(){
num = 10
}
//打印结果 [LOG]: 10
4、双重断言
之前说过any的特性(如果没有,就当我说了):
1、任何数据类型都可以被断言为any
2、any也可以被断言为其他数据类型
基于上面两点,我们可以实现双重断言,代码如下
interface Computer{
deviceName:string
deviceID: number
}
interface Laptop{
lapTopName: string,
lapTopID: number,
}
function checkDevice(huawei: Laptop){
let hwLapTop = huawei as any as Computer
return hwLapTop
}
在上面的例子中,如果我们直接 huawei as Computer绝对是报错的,huawei属于Laptop,而Laptop和Computer互不兼容
而一旦使用了双重断言,就能够突破 「 要想使A能够断言为B,需要A和B兼容 」 的限制,直接将一个类型断言成另外一个类型
使用双重断言之后,很大概率上会出现一些奇怪的问题。就好比上面的代码,在编写阶段没有任何错误,但是一旦运行就会出现问题,所以不是很推荐使用双重断言。
类型守卫
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。简单点说就是类型收紧
类型守卫有几个关键字,typeof、instanceof、in
typeof: 用来判断数据类型,返回判断值的数据类型
let str:string = 'this is a string'
console.log(typeof str) // [LOG]: "string"
let bool:boolean = false
console.log(typeof bool) // [LOG]: "boolean"
let num:number = 123
console.log(typeof num) // [LOG]: "number"
function getFunction(){
console.log('function')
}
console.log(typeof getFunction) // [LOG]: "function"
instanceof: 用来判断实例是否属于某一个类或者某一个方法
class Computer{
deviceName: string
deviceID: number
constructor(deviceName:string,deviceID:number){
this.deviceID = deviceID
this.deviceName = deviceName
}
getDevice(){
return `device:${this.deviceName}(${this.deviceID})`
}
}
let laptop = new Computer('Laptop',499)
if(laptop instanceof Computer){
console.log(laptop.getDevice()) // [LOG]: "device:Laptop(499)"
}
in: 例如('deviceName' in laptop),用来判断某一个属性是否属于目标对象实例中的
class Computer{
deviceName: string
deviceID: number
constructor(deviceName:string,deviceID:number){
this.deviceID = deviceID
this.deviceName = deviceName
}
getDevice(){
return `device:${this.deviceName}(${this.deviceID})`
}
}
let laptop = new Computer('Laptop',499)
console.log(`laptop中包含deviceName: ${'deviceName' in laptop}`)
console.log(`laptop中包含deviceID: ${'deviceID' in laptop}`)
console.log(`laptop中包含deviceColor: ${'deviceColor' in laptop}`)
//打印结果
[LOG]: "laptop中包含deviceName: true"
[LOG]: "laptop中包含deviceID: true"
[LOG]: "laptop中包含deviceColor: false"
结尾
这篇文章到这里算是结束了,如果大家有什么问题或者文章中哪里写错了,希望大家在评论区指正
谢谢大家!