Typescript

TypeScript

前言

一、TypeScript简介和其开发环境的搭建

简介

TypeScript是由微软公司在2012年正式发布,现在也有8年的不断更新和维护了,TypeScript的成长速度是非常快的,现在已经变成了前端必会的一门技能。TypeScript其实就是JavaScript的超集,也就是说TypeScript是建立在JavaScript之上的,最后都会转变成JavaScript。

开发环境的搭建

如果你想使用TypeScript来编写代码,你需要先安装一下它的开发环境,这并不复杂。

1.安装Node的运行环境
2.全局安装typeScript

npm install typescript -g 
或者
yarn global add typescript

3.建立项目目录和编译TS文件

  1. 新建一个目录,比如命名为ts,然后把这个文件夹在VSCode中打开,
  2. 创建demo.ts文件,然后在其中编辑
function testDemo(){
    let str: string = 'Life is subject to ups and downs.';
    console.log(str);
}
testDemo();

这时候你使用node demo.ts是执行不成功的,因为node不能直接运行TypeScript文件,需要用tsc demo.ts转换一下,转换完成后typescript代码被编译成了javascript代码,新生成一个demo.js的文件,这时候你在命令行输入node demo.js,在终端就可以顺利的输出Life is subject to ups and downs.这句话了。
4. ts-node的安装和使用
这样操作的效率实在是太低了,可以使用ts-node插件来解决这个问题,有了这个插件,就不用编译了。
使用npm命令来全局安装,直接在命令行输入下面的命令:
npm install -g ts-node
安装完成后,就可以在命令中直接输入如下命令,来查看结果了。
ts-node demo.ts

二、TypeScript的静态类型

const count: number = 2;
count = 'hello'; // 错误代码,count这个变量在程序中永远都是数字类型,不可以改变了

自定义静态类型

interface activity{
	name: string,
	play: string
}
const LiLei: activity = {
	name: '李雷',
	play: '羽毛球'
}

这时候你如果声明变量,跟自定义不一样,VSCode直接就会报错。需要注意的是,这时候LiLei变量也具有name和play属性了。
这节课你需要记住的是,如果使用了静态类型,不仅意味着变量的类型不可以改变,还意味着类型的属性和方法也跟着确定了。这个特点就大大提高了程序的健壮性,并且编辑器这时候也会给你很好的语法提示,加快了你的开发效率。

三、TypeScript基础静态类型和对象类型

在TypeScript静态类型分为两种,一种是基础静态类型,一种是对象类型,这两种都经常使用,非常重要,我们先来看一下什么是基础静态类型。

基础静态类型

基础静态类型非常简单,只要在声明变量的后边加一个: 号,然后加上对应的类型。

const count: number = 8;

类似这样常用的基础类型还有,这里就举最常用的哦,null,undefinde,symbol,boolean,void这些都是最常用的基础数据类型,至于例子,我这里就不详细的写了,后面碰到,我们再继续讲解。

对象类型

const person:{
    name:string,
    age:number
} = {
    name:'大胖',
    age:18
}
console.log(person.name)

这就是一个经典的对象类型,也是最简单的对象类型。对象类型也可以是数组

const people: String [] = ['lily','韩梅梅','赵明'];

的形式,来定义变量。

class Person{}
const mingming: Person = new Person()

还可以定义一个函数类型,并确定返回值。代码如下:

const aa: ()=>string=()=>{return 'ddd'};
console.log(aa()); // 字符串ddd
const cc: (a)=>any = (a)=>{return a};
console.log(cc(9)); // 数字9
console.log(cc('900')); // 字符串900

总结一下对象类型可以有几种形式:

对象类型
数组类型
类类型
函数类型

这几种形式我们在TypeScript里叫做对象类型。

四、类型注释和类型推断

type annotation 类型注解

这个概念在前面的三节中一直使用,只是没明确这个概念和关系。
学程序并没有这么复杂,我们直接点,新建一个文件demo4.ts ,然后看代码:
let count : number;
count = 123
这段代码就是类型注解,意思是显示的告诉代码,我们的count变量就是一个数字类型,这就叫做类型注解。是不是一下就明白了,其实程序这东西就这么简单,真正复杂的是人。

type inferrence 类型推断

当明白了类型注解的概念之后,再学类型推断就更简单了,先来看一段代码。还是在Demo4.ts文件中写入下面的代码。
let countInference = 123
这时候我并没有显示的告诉你变量countInference是一个数字类型,但是如果你把鼠标放到变量上时,你会发现TypeScript自动把变量注释为了number(数字)类型,也就是说它是有某种推断能力的,通过你的代码TS会自动的去尝试分析变量的类型。这个就彷佛是人的情商比较高,还没等女生表白那,你就已经看出她的心思。
工作使用问题(潜规则)
如果 TS 能够自动分析变量类型, 我们就什么也不需要做了
如果 TS 无法分析变量类型的话, 我们就需要使用类型注解

先来看一个不用写类型注解的例子:

const one = 1;
const two = 2;
const three = one + two;

再来看一个用写类型注解的例子:

function getTotal(one , two){
    return one + two
}
const total = getTotal(1,2)

这种形式,就需要用到类型注释了,因为这里的one和two会显示为any类型。这时候如果你传字符串,你的业务逻辑就是错误的,所以你必须加一个类型注解,把上面的代码写成下面的样子。

function getTotal(one : number, two :number){
    return one + two
}
const total = getTotal(1,2)

这里有的一个问题是,为什么total这个变量不需要加类型注解,因为当one和two两个变量加上注解后,TypeScript就可以自动通过类型推断,分析出变量的类型。

当然TypeScript也可以推断出对象中属性的类型,比如现在写一个小姐姐的对象,然后里边有两个属性。

const XiaoJieJie = {
name:‘刘英’,
age:18
}
写完后你把鼠标放在XiaoJieJie对象上面,就会提示出他里边的属性,这表明TypeScript也分析出了对象的属性的类型。

在写TypeScript代码的一个重要宗旨就是每个变量,每个对象的属性类型都应该是固定的,如果推断不出来的时候你要进行注释。

五、函数参数和返回类型定义

简单类型定义
上节课我们写了一个getTotal的函数,并且对传入的参数作了定义,我们再复习一遍。

新建一个文件demo5.ts,然后写入代码

function getTotal(one : number, two :number){
    return one + two
}

const total = getTotal(1,2)

这时候我们写的代码其实是有一个小坑的,就是我们并没有定义getTotal的返回值类型,虽然TypeScript可以自己推断出返回值是number类型。 但是如果这时候我们的代码写错了,比如写程了下面这个样子。

function getTotal(one : number, two :number){
    return one + two+ ''
}

const total = getTotal(1,2)

这时候total的值就不是number类型了,但是不会报错。有的小伙伴这时候可能会说,可以直接给total一个类型注解,比如写成这个样子。

const total : number =getTotal(1,2) 

这样写虽然可以让编辑器报错,但是这还不是很高明的算法,因为你没有找到错误的根本,这时错误的根本是getTotal()函数的错误,所以合适的做法是给函数的返回值加上类型注解,代码如下:

function getTotal(one : number, two :number) : number{
    return one + two
}

const total = getTotal(1,2)

这段代码就比较严谨了,所以小伙伴们在写代码时,尽量让自己的代码更加严谨。

函数无返回值时定义方法
有时候函数是没有返回值的,比如现在定义一个sayHello的函数,这个函数只是简单的terminal打印,并没有返回值。

 function sayHello(){
     console.log('hello world')
 }

这就是没有返回值的函数,我们就可以给他一个类型注解void,代表没有任何返回值

 function sayHello() : void{
     console.log('hello world')
 }

如果这样定义后,你再加入任何返回值,程序都会报错。

never返回值类型
如果一个函数是永远也执行不完的,就可以定义返回值为never,那什么样的函数是永远也执行不完的那?我们先来写一个这样的函数(比如执行执行的时候,抛出了异常,这时候就无法执行完了)

 function errorFuntion() : never{ 
    throw new Error()
    console.log('Hello World')
 }

还有一种是一直循环,也是我们常说的死循环,这样也运行不完,比如下面的代码:

 function forNever() : never{
     while(true){}
     console.log('Hello 22')
 }

函数参数为对象(解构)时
这个坑有很多小伙伴掉下去过,就是当一个函数的参数是对象时,我们如何定义参数对象的属性类型。我先写个一般javaScript的写法。

 function add ({one , two}){
     return one + two
 }

 const total = add({one:1,two:2})

在浏览器中你会看到直接报错了,意思是total有可能会是任何类型,那我们要如何给这样的参数加类型注解那?最初你可能会这样写。

 function add ({one :number , two :number}){
     return one + two
 }
 const total = add({one:1,two:2})

你在编辑器中会看到这种写法是完全错误的。那正确的写法应该是这样的。

 function add ({one , two } : {one:number, two:number}) :number{
     return one + two
 }
 const three = add({one:1,two:2})

如果参数是对象,并且里边只有一个属性时,我们更容易写错。 错误代码如下:

function getNumber ({one }:number){
     return one;
 }
 const one = getNumber({one:1})

看着好像没什么问题,但实际这是有问题的,正确的代码应该时这样的。

function getNumber ({one } :{one:number}) :number{
     return one;
 }
 const one = getNumber({one:1})

六、数组类型的定义

一般数组类型的定义
现在我们可以定义一个最简单的数组类型,比如就是数字类型,那么就可以这么写:

const numberArr = [1,2,3] 

这时候你把鼠标放在numberArr上面可以看出,这个数组的类型就是number类型。这是TypeScript通过类型推断自己推断出来的如果你要显示的注解,也非常简单,可以写成下面的形式。

const numberArr:number[] = [1,2,3] 

同样道理,如果你的数组各项是字符串,你就可以写成这样。

const stringArr : string [] = [‘a’,‘b’,‘c’]
也就是说你可以定义任意类型的数组,比如是undefined。

const undefinedArr : undefined[]=[undefined,undefined]
这时候问题来了,如果数组中有多种类型,比如既有数字类型,又有字符串的时候。那我们要如何定义那。 很简单,只要加个(),然后在里边加上|就可以了,具体看代码。

const arr: ( number|string )[] = [1,'string',2]

数组简单类型的定义就是这样了,并不难。

数组中对象类型的定义
如果你作过一些项目,你就会知道真实的项目中数组中一定会有对象的出现。那对于这类带有对象的数组定义就稍微麻烦点了。 比如现在我们要定义一个有很多学员的数组,每一个学员都是一个对象。这是的定义就编程了这样。

const xueyuan: {name:string , age:Number}[] = [
    {name:'刘朵',age:18},
    {name:'小芳',age:28}
]

这种形式看起来比较麻烦,而且如果有同样类型的数组,写代码也比较麻烦,TypeScript为我们准备了一个概念,叫做类型别名(type alias)。

比如刚才的代码,就可以定义一个类型别名,定义别名的时候要以type关键字开始现在定义一个Lady的别名

type  Lady  =  {name:string , age:Number};

有了这样的类型别名以后哦,就可以把上面的代码改为下面的形式了。

type Lady = {name:string , age:Number};

const xiaoJieJies : Lady[] = [
{name:‘刘英’,age:18},
{name:‘谢大脚’,age:28}
]
这样定义是完全起作用的,比如我们下面在对象里再加入一个属性,这时候编译器就会直接给我们报错了。

这时候有的小伙伴就会问了,我用类进行定义可以吗?答案是可以的,比如我们定义一个Madam的类,然后用这个类来限制数组的类型也是可以的。

class Madam {
    name: string ;
    age:number ;
}

const xiaoJieJies : Madam[] = [
    {name:'刘英',age:18},
    {name:'谢大脚',age:28}
]

可以看到结果,这么写也是完全可以的。

七、元组的使用和类型约束

把数组中的每个元素类型的位置给固定住了,这就叫做元组
TypeScript中提供了元组的概念,这个概念是JavaScript中没有的。但是不要慌张,其实元组在开发中并不常用,也可能是我的经历还不够。一般只在数据源是CSV这种文件的时候,会使用元组。其实你可以把元组看成数组的一个加强,它可以更好的控制或者说规范里边的类型

我们先来看一个数组和这个数组注解的缺点,比如我们有一个学员数组,数组中有姓名、职业和年龄,代码如下:

const xueyuan = ['dajiao','teacher',28]

这时候把鼠标放到xueyuan变量上面,可以看出推断出来的类型。我们就用类型注解的形式给他作一个注解,代码如下:

const xueyuan:(string | number)[] = [‘dajiao’,‘teacher’,28]
这时候你已经增加了代码注解,但是这并不能很好的限制,比如我们把代码改成下面的样子,TypeScript依然不会报错。
const xueyuan:(string | number)[] = [‘dajiao’,28,‘teacher’]
我们只是简单的把数组中的位置调换了一下,但是TypeScript并不能发现问题,这时候我们需要一个更强大的类型,来解决这个问题,这就是元组。
元组和数组类似,但是类型注解时会不一样。

const xueyuan: [string,string ,number]  = ['dajiao','teacher',28]

这时候我们就把数组中的每个元素类型的位置给固定住了,这就叫做元组

八、interface 接口

TypeScript中的接口,就是用来规范类型的
interface接口初步了解
假设我们现在要做一个简历的自动筛选程序,很简单,年龄小于25岁,但是身高大于170公分的,可以进入面试环节。我们呢最开始的写法是这样的。(新建一个demo8.ts,然后编写如下代码)

const screenResumes=(name:string, age:number,height: number)=>{
	age<25 && height>170 && console.log(name+'进入面试');
	age>=25 || height<=170 && console.log(name+'你被淘汰')
}
screenResumes('大个', 22, 185);

假如,此时又增加了需求,必须能看到这些人的简历,于是又写了这样一个方法。

const getResume=(name:string, age:number,height: number)=>{
    console.log(name+'年龄是:'+age);
    console.log(name+'身高是:'+height);
}
getResume('Hedy',20,184);

这时候问题来了,程序开发中一直强调“代码重用”,两个方法用的类型注解一样,需要作个统一的约束。上面用过一个类型别名的知识可以解决代码重复的问题,这次我们就学习一个更常用的语法接口(Interface)。
我们可以把这两个重复的类型注解,定义成统一的接口。代码如下

interface Person{
    name: string;
    age: number;
    height: number;
}

有了接口后,我们的程序也要作一些修改,需要写成下面的样子。这样就更像是面向对象编程了。

interface Person{
    name: string;
    age: number;
    height: number;
}
const screenResumes=(person: Person)=>{
	person.age<25 && person.height>170 && console.log(person.name+'进入面试');
	person.age>=25 || person.height<=170 && console.log(person.name+'你被淘汰')
}
const getResume=(person: Person)=>{
    console.log(person.name+'年龄是:'+person.age);
    console.log(person.name+'身高是:'+person.height);
}
const person={
    name: '大个',
    age: 22,
    height: 185
};
screenResumes(person);
getResume(person);

这时候我们代码就显得专业了很多,以后再用到同样的接口也不怕了,直接使用就可以了。
接口和类型别名的区别:
这两个语法和用处好像一样,但是也有不一样的地方。
类型别名可以直接给类型,比如string,而接口必须代表对象。
比如我们的类型别名可以写出下面的代码:

type Person= string

但是接口就不能这样写,它必须代表的是一个对象,也就是说,你初始化person的时候,必须写出下面的形式.

const person={
    name:'holy',
    age:18,
    height:194
}

接口非必选值得定义(在:号前加一个?,比如age?: number)

interface Person{
    name: string;
    age?: number; // 年龄不是必须得定义的
    height: number;
}

允许加入任意值
简历一般是有自由发挥的空间的,所以这时候需要一些任意值,就是自己愿意写什么就写什么。这时候interface接口也是支持的。方法如下: 我们接着上次的代码,新建一个Demo9.ts,然后把上次的代码拷贝过来。

interface Person{
    name: string;
    age?: number; // 年龄不是必须得定义的
    height: number;
    [propname:string]: any;
}

[propname:string]: any;这个的意思是,属性的名字是字符串类型,属性的值可以是任何类型。

这时候我们在对象里给一个性别,代码如下:

const person={
name: ‘大个’,
age: 22,
height: 185,
sex: ‘女’
};
接口里的方法
接口里不仅可以存属性,还可以存方法,比如这时候有个say()方法,返回值是string类型。这时候你就不要再想成简历了,你需要更面向对象化的编程,想象成一个人。

interface Person{
    name: string;
    age?: number; // 年龄不是必须得定义的
    height: number;
    [propname:string]: any;
    say(): string;
}

加上这个say()方法后,程序马上就会报错,因为我们对象里没有say方法。那我们就要给对象一个say方法

const person={
    name: '大个',
    age: 22,
    height: 185,
    sex: '女',
    say(){
		return '您们好,我是'+this.name
	}
};
console.log(person.say());

接口和类的约束
JavaScript从ES6里是有类这个概念的,类可以和接口很好的结合,先来一个例子。

class Person1 implements Person{
}
这时候类会直接报错,需要把这个类写的完全点。
class Person1 implements Person{
    name='小宋';
    age=23;
    height=178;
    say(){return this.name}
}
console.log(new Person1().say()); // 小宋

接口间的继承
接口也可以用于继承的,比如你新写一个Teacher接口,继承于Person接口。

interface Teacher extends Person{
    teach():string
}

十、类的概念和使用

TypeScript中类的概念和ES6中原生类的概念大部分相同,但是也额外增加了一些新的特性。
类的基本使用
新建一个文件,叫做demo10.ts,然后定义一个最简单的Lady类,这里要使用关键字class,类里边有姓名属性和一个得到姓名的方法,代码如下:

class Lady{
    content='Hi,帅哥'
    sayHello(){
        return this.content
    }
}

const goddess = new Lady()
console.log(goddess.sayHello())

写完代码后,可以使用ts-node demo10.ts来查看一下结果。
这是一个最简单的类了,如果你有些编程经验,对这个一定很熟悉,工作中几乎每天都会用到。
类的继承
这里提前说一下TypeScrip的继承和ES6中的继承是一样的。关键字也是extends,比如我们这里新建一个XiaoJieJie的类,然后继承自Lady类,在XiaoJieJie类里写一个新的方法,叫做sayLove,具体代码如下。

class Lady{
    content='Hi,帅哥'
    sayHello(){
        return this.content
    }
}
class XiaoJieJie extends Lady{
    sayLove(){
        return 'I love you'
    }
}
const goddess = new XiaoJieJie()
console.log(goddess.sayHello())
console.log(goddess.sayLove())

类写好以后,我们声明的对象是XiaoJieJie这个类,我们同时执行sayHello()和sayLove()都是可以执行到的,这说明继承起作用了。
类的重写
讲了继承,那就必须继续讲讲重写,重写就是子类可以重新编写父类里边的代码。现在我们在XiaoJieJie这个类里重写父类的sayHello()方法,比如现在我们觉的叫的不够亲切,我们改成下面这个样子。

class XiaoJieJie extends Lady{
    sayLove(){
        return 'I love you!'
    }
    sayHello(){
        return  'Hi , honey!'
    }
}

然后我们再次运行ts-node demo10.ts来查看结果。
super关键字的使用
我们再多讲一点,就是super关键字的使用,比如我们还是想使用Lady类中说的话,但是在后面,加上你好两个字就可以了。这时候就可以使用super关键字,它代表父类中的方法。那我们的代码就可以写成这个样子了。

class XiaoJieJie extends Lady{
    sayLove(){
        return 'I love you!'
    }
    sayHello(){
        return  super.sayHello()+'。你好!'
    }
}

十一、类的访问类型

类的访问类型就是基于三个关键词private、protected和public,也是三种访问类型
public访问属性
如果不在类里对name的访问属性进行定义,那么它就会默认是public访问属性。

这就相当于下面的这段代码:

class Person {
    public name:string;
}

public从英文字面的解释就是公共的或者说是公众的,在程序里的意思就是允许在类的内部和外部被调用.

比如我们在类内调用,我们在写一个sayHello的方法,代码如下:

class Person {
    public name:string;
    public sayHello(){
        console.log(this.name + ' say Hello')
    }
}

这是的this.name就是类的内部调用。我们在下面在执行一下这个方法person.sayHello(),终端中可以看到一切正常运行了,顺利打印出了22.com say Hello这句话。

在类的外部调用,我们就可以很简单的看出来了,比如下面的代码,从注释横线下,全部是类的外部。

class Person {
    public name:string;
    public sayHello(){
        console.log(this.name + 'say Hello')
    }
}
//-------以下属于类的外部--------
const person = new Person()
person.name = '22.com'
person.sayHello()
console.log(person.name)

结果我就不演示了,一定是可以被调用的,接下来我们再来看private属性。

private访问属性
private 访问属性的意思是,只允许在类的内部被调用,外部不允许调用

比如现在我们把name属性改成private,这时候在类的内部使用不会提示错误,而外部使用VSCode直接会报错。

class Person {
    private name:string;
    public sayHello(){
        console.log(this.name + 'say Hello')  //此处不报错
    }
}
//-------以下属于类的外部--------
const person = new Person()
person.name = '22.com'    //此处报错
person.sayHello()
console.log(person.name)  //此处报错

protected访问属性讲解
protected 允许在类内及继承的子类中使用

做一个例子,把name的访问属性换成protected,这时候外部调用name的代码会报错,内部的不会报错,和private一样。这时候我们再写一个Teacher类,继承于Person,代码如下:

class Person {
    protected name:string;
    public sayHello(){
        console.log(this.name + 'say Hello')  //此处不报错
    }
}

class Teacher extends Person{
    public sayBye(){
        this.name;
    }
}

这时候在子类中使用this.name是不报错的。
通过这个小例子相信你一定指导什么是类的内部和类的外部,也知道了三个访问类型的区别了。先掌握了类内和类外的概念后,这三个访问类型就非常好理解了。

十二、类的构造函数

构造函数就是在类被初始化的时候,自动执行的一个方法。
类的构造函数
新建立一个页面Demo12.ts,然后在页面里新建一个Person类,类的里边定义一个name,但是name我们并不给他值,然后我们希望在new出对象的时候,直接通过传递参数的形式,给name赋值,并打印出来。这时候我们就需要用到构造函数了,构造函数的关键字是constructor。

class Person{
    public name :string ;
    constructor(name:string){
        this.name=name
    }

}

const person= new Person('22')
console.log(person.name)

写完后使用ts-node demo12.ts进行查看,应该可以打出22的字样。这是最常规和好理解的写法,那有没有更简单的写法那?当然有。

class Person{
    constructor(public name:string){
    }
}

const person= new Person('22')
console.log(person.name)

这种写法就相当于你定义了一个name,然后在构造函数里进行了赋值,这是一种简化的语法,在工作中我们使用这种语法的时候会更多一些。

类继承中的构造器写法
普通类的构造器我们已经会了,在子类中使用构造函数需要用super()调用父类的构造函数。这时候你可能不太理解我说的话,我们还是通过代码来说明。

class Person{
    constructor(public name:string){}
}

class Teacher extends Person{
    constructor(public age:number){
        super('22')
    }
}

const teacher = new Teacher(18)
console.log(teacher.age)
console.log(teacher.name)

这就是子类继承父类并有构造函数的原则,就是在子类里写构造函数时,必须用super()调用父类的构造函数,如果需要传值,也必须进行传值操作。就是是父类没有构造函数,子类也要使用super()进行调用,否则就会报错

class Person{}

class Teacher extends Person{
    constructor(public age:number){
        super()
    }
}

const teacher = new Teacher(18)
console.log(teacher.age)

十三、类的Getter、Setter和static使用

学了类的访问类型Private,这个东西如何使用?其实它的最大用处是封装一个属性,然后通过Getter和Setter的形式来访问和修改这个属性。
类的Getter和Setter
新建一个文件,然后声明一个Person类,都知道人们的年龄是不能随便告诉人,所以使用了private,这样别人就都不知道她的真实年龄,而只有他自己知道。
代码如下:

class Person{
    constructor(private _age: number){}
}

如果别人想知道 ,就必须通过getter属性知道,注意我这里用的是属性,对他就是一个属性。getter属性的关键字是 get,后边跟着类似方法的东西 ,但是你要注意,它并不是方法,归根到底还是属性。

class Person {
    constructor(private _age: number) { }
    get age1() {
        return this._age - 10
    }
    set age1(age: number) {
        this._age = age + 3
    }
}

const dajiao = new Person(28)
dajiao.age1 = 25
console.log(dajiao.age1); // 18

类中的static
学习类,都知道要想使用这个类的实例,就要先new出来(),但是有时候人们就是喜欢走捷径。
比如我们先写一下最常规的写法:

class Girl{
    sayLove(){
        return 'I love you.'
    }
}
const girl = new Girl();
console.log(girl.sayLove());

但是现在你不想new出对象,而直接用这个方法,那TypeScript为你提供了快捷的方式,用static声明的属性和方法,不需要进行声明对象,就可以直接使用,代码如下。

class Girl{
    static sayLove(){
        return 'I love you.'
    }
}
console.log(Girl.sayLove());

十四、类的只读属性和抽象类

抽象类和父类很像,都需要继承,但是抽象类里一般都有抽象方法。继承抽象类的类必须实现抽象方法才可以。
类里的只读属性readonly
新建一个文件,然后写下面一个类,并进行实例化和赋值操作,代码如下:

class Person {
    constructor(public name:string ){ }
}

const person = new Person('22')
console.log(person.name)

写完后我们可以在终端(Terminal)中看一下结果,结果就应该是22。
比如我现在有一个需求,就是在实例化对象时赋予的名字,以后不能再更改了,也就是我们常说的只读属性。我们先来看现在这种情况是可以随意更改的,比如我写下面的代码。

class Person {
    constructor(public name:string ){ }
}

const person = new Person('22')
person.name= '谢广坤'
console.log(person.name)

这时候就可以用一个关键词readonly,也就是只读的意思,来修改Person类代码。

class Person {
    public readonly _name :string;
    constructor(name:string ){ 
        this._name = name;
    }
}

const person = new Person('22')
person._name= '谢广坤'
console.log(person._name)

这样写完后,VSCode就回直接给我们报错,告诉我们_name属性是只读属性,不能修改。
抽象类的使用
什么是抽象类那?我给大家举个例子,比如开了一个洗浴中心,里边有服务员,有初级技师,高级技师,每一个岗位我都写成一个类,那代码就是这样的。

class Waiter{
}
class BaseTeacher{
}
class seniorTeacher{
}

作为老板,要求无论是什么职位,都要有独特的技能,比如服务员就是给顾客倒水,初级技师要求会泰式按摩,高级技师要求会SPA全身按摩。这是一个硬性要求,但是每个职位的技能有不同,这时候就可以用抽象类来解决问题。
抽象类的关键词是abstract,里边的抽象方法也是abstract开头的,现在我们就写一个Girl的抽象类。

abstract class Person{
    abstract skill()  //因为没有具体的方法,所以我们这里不写括号
}

有了这个抽象类,三个类就可以继承这个类,然后会要求必须实现skill()方法,代码如下:

abstract class Person{
    abstract skill()  //因为没有具体的方法,所以我们这里不写括号
}
class Waiter extends Person{
    skill(){
        console.log('大爷,请喝水!')
    }
}
class BaseTeacher extends Person{
    skill(){
        console.log('大爷,来个泰式按摩吧!')
    }
}
class seniorTeacher extends Person{
    skill(){
        console.log('大爷,来个SPA全身按摩吧!')
    }
}

十五、配置文件-初识tsconfig.json

生成tsconfig.json文件
这个文件是通过tsc --int命令生成的,在桌面上新建一个文件夹,然后打开VSCode, 把文件拖到编辑器中,然后打开终端,输入tsc --init。
输入完成后,就会出现tsconfig.json文件。
其实它就是用来配置如何对ts文件进行编译的,我们都叫它typescript的编译配置文件。
如果此时你的tsc执行不了,很有可能是你没有全局安装TypeScript,可以全局安装一下。
让tsconfig.json文件生效
可以在文件夹根目录建立一个demo.ts文件,然后编写一些最简单的代码,代码如下:

const person : string = '22'

这时候我们不在使用ts-node直接执行了,需要用tsc demo.ts进行编译,编译后会得到demo.js文件。 生成的代码如下:

var person = '22';

这时候好像一切都是正常的,但是我要告诉你的真相是tsconfig.json这个编译配置文件并没有生效。
此时我们打开tsconfig.json文件,找到complilerOptions属性下的removeComments:true选项,把注释去掉。
这个配置项的意思是,编译时不显示注释,也就是编译出来的js文件不显示注释内容。
现在你在文件中加入一些注释,比如:
// I love 22
const person : string = ‘22’
这时候再运行编译代码tsc demo.ts,编译后打开demo.js文件,你会发现注释依然存在,说明tsconfig.json文件没有起作用。
如果要想编译配置文件起作用,我们可以直接运行tsc命令,这时候tsconfig.json才起作用,可以看到生成的js文件已经不带注释了。

include 、exclude和files
如果我们的跟目录下有多个ts文件,我们却只想编译其中的一个文件时,如何作?
我们在项目根目录,新建一个文件demo2.ts文件,然后也写一段最简单的ts代码。

const person2 :string = '22.com'

如果这时候我们在终端里运行tsc,虽然tsconfig.json生效了,但是两个文件都被我们编译了。这不是你想要的结果,我们可以用三种办法解决这个问题。
第一种:使用include配置
include属性是用来指定要编译的文件的,比如现在我们只编译demo.ts文件,而不编译demo2.ts文件,就可以这样写。
写配置文件时有个坑需要注意,就是配置文件不支持单引号,所以里边都要使用双引号。

{
  "include":["demo.ts"],
  "compilerOptions": {
      //any something
      //........
  }
}

这时候再编译,就只编译demo.ts文件了。
第二种:使用exclude配置
include是包含的意思,exclude是不包含,除什么文件之外,意思是对设置的属性之外的文件才进行编译。比如你还是要编译demo.ts文件,这时候的写法就应该是这样了。

{ 
	"exclude": ["demo2.ts"],
    "compilerOptions":{。。。。}
}

第三种:使用files配置
files的配置效果和include几乎一样,我是没找出有什么不同,只要配置到里边的文件都可以编译,

{
  "files":["demo.ts"],
  "compilerOptions": {
      //any something
      //........
  }
}

十六、配置文件-初识compilerOptions配置项

compilerOptions配置项,是告诉TypeScript具体如何编译成js文件的,里边的配置项非常多,下面先来几个简单的配置项。
removeComments属性
removeComments是compilerOptions里的一个子属性,它的用处是告诉TypeScript对编译出来的js文件是否显示注释(注解)。比如我们现在把removeComments的值设置为true,就是在js中不显示注释。
编写一个demo.ts文件,代码如下:

// I‘m 22
const person: string = "22";

写完注释后,直接在终端里,输入tsc,输入完成后,很快就会生成一个demo.js文件,打开后会看到下面的代码。

"use strict";
var person = "22";

写的注释并没有编译到demo.js里。如果我们反之,把removeComments的值,设置为false,这时候demo.js里就会有注释内容了。

"use strict";
// I‘m 22
var person = "22";

strict属性
strict属性如果设置为true,就代表我们的编译和书写规范,要按照TypeScript最严格的规范来写,如果我们把这个设置为false或者注释掉,意思是我们可以设置一些不严格的写法。
noImplicitAny属性
nolmplicitAny属性的作用是,允许你的注解类型any不用特意表明,只听概念很难理解。
为了更好的说明,举个例子,在demo.ts里,删除刚才的代码,然后写一个方法,方法的参数我们设置成任意类型(any)。

function getName(name){
	return name;
}

这时候我们的TypeScript是进行报错的,我们用tsc编译也是报错的。这就是因为我们开启了strict:true,我们先注释掉,然后把noImplicitAny的值设置为false,就不再报错了。
如果设置为noImplicitAny:true,意思就是值就算是any(任意值),你也要进行类型注释。

function getName(name: any){
	return name;
}

strictNullChecks属性
我们先把strictNullChecks设置为false,它的意思就是,不强制检查NULL类型。举个例子,让你能一下子就明白,还是删除demo.ts里的代码,然后编写代码:

const huan: string = null;

代码写完后,你会发现这段代码是不报错的,如果是以前 ,一定是报错的,这就是我们配置了“不强制检验null类型”。如果你设成strictNullChecks:true,这时候就报错了。
这次我们就是简单的认识一下compilerOptions属性的配置,用的时候会查API就可以了。下面我们继续学习配置文件。
ts-node遵循tsconfig.js文件
tsc fileName 是没办法遵循tsconfig.js文件的,那ts-node是否遵循?
答案是,ts-node是遵循的。

十七、配置文件-compilerOptions配置内容详解

rootDir和outDir
现在你的js文件直接编译到了根目录下,和ts文件混在了一起。我们当然是不喜欢这种方法的,工作中我们希望打包的js都生成在特定的一个文件夹里,比如build。

这时候你就可以通过配置outDir来配置,当然你也可以通过rootDir来指定ts文件的位置,比如我们把所有的 ts 文件都放到 src 下。那配置文件就应该这样写。

{
    "outDir": "./build" ,
    "rootDir": "./src" ,
}

这时候你再在终端中输入tsc,就会有不同的效果了。
编译ES6语法到ES5语法-allowJs
现在你在src目录下用ES6的语法写了一个demo2.js文件,代码如下:

export const name='wulei';

如果你不做任何配置,这时候试用tsc是没有效果的。你需要到tsconfig.js文件里进行修改,修改的地方有两个。

"target":"es5", //这一项默认是开启的,你必须要保证它的开启,才能转换成功
"allowJs":true, // 这个配置项的意思是联通

在这里插入图片描述

sourceMap属性
如果把sourceMap的注释去掉,在打包的过程中就会给我们生成sourceMap文件。
sourceMap简单说,就是一个信息文件,里面存储着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是 转换后的代码。这无疑给开发者带来了很大方便。
noUnusedLocals和noUnusedParameters
比如现在我们修改demo.ts文件的代码,改为下面的样子。

const liming: string=null;
export const name="liming";

这时候你会发现liming这个变量没有任何地方使用,但是我们编译的话,它依然会被编译出来,这就是一种资源的浪费。

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.name = void 0;
var liming = null;
exports.name = "liming";

这时候我们可以开启noUnusedLocals:true,若有未使用的局部变量则抛错。开启后我们的程序会直接给我们提示不能这样编写代码,有没有使用的变量。
noUnusedParameters是若有未使用的参数则报错,方法和noUnusedLocals:true一样,小伙伴们自己尝试吧。
如果你需要全面的了解,可以查看这个网址:
https://www.tslang.cn/docs/handbook/compiler-options.html (编译选项详解)

十八、联合类型和类型保护

只有联合类型存在的情况下,才需要类型保护。普通的类型注解,并不需要我们这种特殊操作。那先来看一下什么是联合类型。
联合类型展示
所谓联合类型,可以认为一个变量可能有两种或两种以上的类型。用代码举个例子,声明两个接口Waiter(服务员)接口和Teacher(技师)接口,然后在写一个judgeWho(判断是谁)的方法,里边传入一个animal(任意值),这时候可以能是Waiter,也可能是Teacher。所以我们使用了
联合类型,关键符号是|(竖线)。

interface Waiter {
  anjiao: boolean;
  say: () => {};
}
interface Teacher {
  anjiao: boolean;
  skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {}
通过这个简单的例子,你应该知道什么是联合类型了。
function judgeWho(animal: Waiter | Teacher) {
  animal.say();
}

但这时候问题来了,如果我直接写一个这样的方法,就会报错,因为judgeWho不能准确的判断联合类型具体的实例是什么。
这时候就需要再引出一个概念叫做类型保护,类型保护有很多种方法,这节讲几个最常使用的。
类型保护-类型断言
类型断言就是通过断言的方式确定传递过来的准确值,比如上面的程序,如果会anjiao(按脚),说明他就是技师,这时候就可以通过断言animal as Teacher,然后直接调用skill方法,程序就不再报错了。同样如果不会按脚,说明就是不同的服务员,这时候调用say()方法,就不会报错了。这就是通过断言的方式进行类型保护。也是最常见的一种类型保护形式。具体看代码:

interface Waiter {
  anjiao: boolean;
  say: () => {};
}
interface Teacher {
  anjiao: boolean;
  skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {
  if (animal.anjiao) {
    (animal as Teacher).skill();
  }else{
    (animal as Waiter).say();
  }
}

类型保护-in 语法
我们还经常使用in语法来作类型保护,比如用if来判断animal里有没有skill()方法。
这里你可以赋值上面的judgeWho()方法,然后改一下名字,我这里改成了judgeWhoTwo()方法,具体程序如下:

function judgeWhoTwo(animal: Waiter | Teacher) {
  if ("skill" in animal) {
    animal.skill();
  } else {
    animal.say();
  }
}

这里的else部分能够自动判断,得益于TypeScript的自动判断。
类型保护-typeof语法
先来写一个新的add方法,方法接收两个参数,这两个参数可以是数字number也可以是字符串string,如果我们不做任何的类型保护,只是相加,这时候就会报错。代码如下:

function add(first: string | number,second: string | number){
	return first+second;
}

解决这个问题,就可以直接使用typeof来进行解决。

function add(first: string | number, second: string | number) {
  if (typeof first === "string" || typeof second === "string") {
    return `${first}${second}`;
  }
  return first + second;
}

类型保护-instanceof 语法
比如现在要作类型保护的是一个对象,这时候就可以使用instanceof语法(针对类)。

class Foo {
  foo = 123;
}

class Bar {
  bar = 123;
}

function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error 类型“Foo”上不存在属性“bar”
  } else {
    // 这个块中,一定是 'Bar'
    console.log(arg.foo); // Error 类型“Bar”上不存在属性“foo”
    console.log(arg.bar); // ok
  }
}

doStuff(new Foo());
doStuff(new Bar());

十九、Enum枚举类型详细讲解

它不带有初始化器且它之前的枚举成员是一个 数字常量。 这种情况下,当前枚举成员的值为它上一个枚举成员的值加1。当然还有其他,比如字符串枚举,它就没有自增长行为。

enum Status{
    BATH, // BATH=3 这样的话就会从3开始
    SPA,
    ZHENGGU
}
function getServe(status: any){
    if(status === Status.BATH){
        return "bath"
    }else if(status === Status.SPA){
        return "SPA"
    }else if(status === Status.ZHENGGU){
        return "zhenggu"
    }
    return "none"
}
const result = getServe(4);
console.log(`我要去${result}`)

二十、TypeScript函数中使用泛型

使用之前的方式:

function join(first: string | number, second: string | number){
    return `${first}${second}`;
}
console.log(join("xiaofang", ".com"));

使用泛型:

// 泛型
function join<T, P>(first: T, second: P){
    return `${first}${second}`;
}
console.log(join<string, string>("xiaofang", ".com"))

泛型中数组的使用

function myFun<T>(params: T[]){
//也可以params: Array<T>
    return params;
}
console.log(myFun<string>(["xiaofang", ".com"]))

二十一、TypeScript在类中使用泛型

interface Girl {
    name: string;
}
class SelectGirl<T extends Girl>{
    constructor(private girls: T[]) { }
    getGirl(index: number): string {
        return this.girls[index].name;
    }
}

const selectGirl = new SelectGirl([
    { name: '潇潇' },
    { name: '小小' },
    { name: '笑笑' },
]);
console.log(selectGirl.getGirl(1));

二十二、初识TypeScript的命名空间

新建一个文件夹,比如tsweb,打开终端,在该文件夹下运行命令npm init -y生成package.json文件,运行tsc -init生成tsconfig.json文件,然后创建src文件夹和build文件夹,及在根目录下创建index.html文件,在src文件夹下创建page.ts文件,将tsconfig.json文件中的outDir及rootDir注释打开:“outDir”: “./build”, “rootDir”: “./src”,
index.html文件内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src='./build/page.js'></script>
</head>
<body>
    <script>
        new Home.Page();
    </script>
</body>
</html>

page.ts文件内容如下:

namespace Home{
    class Header{
        constructor(){
            const elem = document.createElement('div');
            elem.innerText='This is Header';
            document.body.appendChild(elem);
        }
    }
    class Content{
        constructor(){
            const elem = document.createElement('div');
            elem.innerText='This is Content';
            document.body.appendChild(elem);
        }
    }
    class Footer{
        constructor(){
            const elem = document.createElement('div');
            elem.innerText='This is Footer';
            document.body.appendChild(elem);
        }
    }
   export class Page{
        constructor(){
            new Header();
            new Content();
            new Footer();
        }
    }
}

在终端输入tsc,编译一下ts文件
在控制台中输入Home.Page:

ƒ Page() {
            new Header();
            new Content();
            new Footer();
        }

但是Home.Footer为undefined
命名空间起作用了

二十三、深入TypeScript的命名空间

接着上面的,在src文件夹下面创建一个components.ts文件,然后内容为:

namespace Components{
	export namespace SubComponents{
        export class Test{}
    }
    export  class Header{
        constructor(){
            const elem = document.createElement('div');
            elem.innerText='This is Header';
            document.body.appendChild(elem);
        }
    }
    export  class Content{
        constructor(){
            const elem = document.createElement('div');
            elem.innerText='This is Content';
            document.body.appendChild(elem);
        }
    }
    export class Footer{
        constructor(){
            const elem = document.createElement('div');
            elem.innerText='This is Footer';
            document.body.appendChild(elem);
        }
    }
}

page.ts中内容改为

namespace Home{
   export class Page{
        constructor(){
            new  Components.Header();
            new Components.Content();
            new Components.Footer();
        }
    }
}

同时在index.html文件中引入

在tsconfig.json文件中取消outFile注释,此时需要同时将module改为amd,因为设置outFile后不支持module:commonjs

   "module": "amd",   /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "outFile": "./build/page.js",   /* 输出到一个文件中 */

在终端输入tsc,编译一下ts文件

二十四、TypeScript如何使用import语法

去掉上面components.ts和page.ts中的命名空间
components.ts文件代码如下:

export class Header {
    constructor() {
        const elem = document.createElement('div');
        elem.innerText = 'This is Header';
        document.body.appendChild(elem);
    }
}
export class Content {
    constructor() {
        const elem = document.createElement('div');
        elem.innerText = 'This is Content';
        document.body.appendChild(elem);
    }
}
export class Footer {
    constructor() {
        const elem = document.createElement('div');
        elem.innerText = 'This is Footer';
        document.body.appendChild(elem);
    }
}

同时相应的修改page.ts文件

import { Header, Content, Footer } from './components';

export default class Page {
    constructor() {
        new Header();
        new Content();
        new Footer();
    }
}

运行tsc命令
此时会发现编译后对应的page.js文件内容define(“components”, 。。。
属于amd,需要引入require,在index.html中引入
同时修改index.html文件中的body下的script

<body>
    <script>
        require(['page'], function(page){
            new page.default();
        })
    </script>
</body>

二十五、用Parcel打包TypeScript代码

新建一个文件夹,如tstest,然后npm init -y生成package.json文件,tsc -init生成tsconfig.json文件,在终端运行yarn add --dev parcel@next安装最新的parcel,在根目录下创建build文件夹和src文件夹,在src下创建index.html文件和page.ts文件,在index.html文件中引入
page.ts文件中代码如下:

const teacher: string='xiaoming';
console.log(teacher);

修改tsconfig.json文件

"outDir": "./build",    
 "rootDir": "./src", 

修改package.json文件,

"scripts": {
    "test": "parcel ./src/index.html"
  },

在终端运行tsc命令,然后运行yarn test,会进行打包操作,将内容打包至dist文件夹下,目录结构如下:
在这里插入图片描述

二十六、在TypeScript中使用Jquery库

接着上面的继续,在index.html文件中引入jquery,

page.ts文件中代码如下:

$(function(){
    alert('xiaoming.com');
})

会出现下面的错误
在这里插入图片描述
解决办法:
方法一:
npm i @types/jquery
方法二:
在page.ts文件中添加declare var $: any;

declare var $: any;
$(function(){
    alert('xiaoming.com');
})
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值