还不会ts?一文带你打开ts的大门

Posted 星期一研究室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了还不会ts?一文带你打开ts的大门相关的知识,希望对你有一定的参考价值。


一文带你打开ts的大门

序言

众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地, bugUpUp了。于是,在过去的这两年, ts 悄悄的崛起了。

周一随着一波热潮,也开始进入了 ts 的世界,不得不感叹 ts 的静态美。

下面的文章中将讲解我对 TS 入门的一些归纳总结。一起来了解一下吧!

一、什么是TypeScript?

1、编程语言的类型

动态类型语言(Dynamic Typed Language)静态类型语言(Statically Typed Langeage)
javascriptC,C++,C#,JAVA

2、TypeScript究竟是什么?

  • Typescript,即 Javascript that scales

  • ts 把不看重类型的动态语言 JS 转变成关注类型的静态语言

  • 可以说ts是静态类型风格的类型系统

  • es6es10 甚至是 esnext 的语法支持;

  • 兼容各种浏览器,各种系统,各种服务器,完全开源。

二、为什么要学习TypeScript?

1、程序更容易理解

动态语言存在函数或者方法中其输入输出的参数类型等问题,同时,动态语言还受到各种各样的约束,比如需要手动调试等等。那么有了 ts ,代码本身就可以解决上述问题了, ts 让程序更容易理解,程序理解我们,我们就可以少干很很多事情。

就像我们在与别人交谈时,假如我们逻辑很清晰的表达给对方,对方马上听懂了,并且理解了我们,我们也很省力,不用长篇大论的介绍。

2、效率更高

ts 可以在在不同的代码块和定义中进行跳转,并且代码有补全功能

同时, ts 还有丰富的接口提示,可以通过使用 . 来提示所有的接口内容。

3、更少的错误

ts编程期间,就可以发现大部分的错误。这样就可以杜绝掉一些比较常见的错误,也使得后面程序运行更加通畅。

4、非常好的包容性

ts 可以完全地兼容 Javascript ,同时,如果要引入像 JQuery 之类的第三方库时,可以单独编写类型文件来引入这些库。

5、一点小缺点

相比于 js 来讲, ts 在学习之初,需要去习惯一些规范,短期内会增加一点学习成本。但短期的学习成本增加将会使得在后期的开发当中减少很多不必要的错误和麻烦,间接的也为自己的开发带来很大的益处。

闲谈到此结束,让我们一起来进入 ts 的世界吧!

三、typescript入门

1、如何安装TypeScript

npm install -g typescript 

2、查看版本号

tsc -v

3、运行ts文件

npm i ts-node@8.4.1 -g
ts-node demo.ts

4、创建ts项目

npm init -y
tsc --init
npm i ts-node -D
npm i typescript -D

四、Typescript数据类型

1、原始数据类型和Any类型

(1)原始数据类型

//定义一个布尔值数据
let isDone: boolean = false

//定义一个数字类型
let age: number = 20

//定义字符串类型
let firstName: string = 'monday'
let message: string = `Hello, $firstName`

//定义undefind和null类型
let u: undefined = undefined
let n: null = null

//给数字赋值undefid
let num: number = undefined

(2)Any 类型

如果我们有时候不能确定一个数据是什么类型的话,那么我们可以用any类型来定义。比如:

//定义一个any类型数据
let notSure: any = 4
notSure = 'maybe a string'
notSure = true

notSure.myName
notSure.getName()

2、数组和元组

(1)数组

//声明一个数字类型的数组
//注意:后面的数组只能传递数字,传递其他类型的数据都会报错
let arrOfNumbers: number[] = [1, 2, 3];
arrOfNumbers.push(3)

function test()
    //arguments 为类数组
    console.log(arguments)

(2)元组

//确定一个元组里面的内容和数量,下面表示确定user这个元组必须且只能接收两个参数
//同时第一个属性必须是String类型,第二个属性是Number类型
let user: [String, Number] = ['abc', 13]

3、interface 接口

interface的定义:

  • 对象Object 的形状 (shape) 进行描述;
  • Duck Typing(鸭子类型)。

我们来看一段代码:

interface Person
    // readonly表示只读状态
    readonly id: number,
    name: String,
    //加一个问号表示该参数可选可不选
    age?: number


let monday: Person = 
    id: 1,
    name: 'monday',
    age: 18


monday.id = 12323; //因为加了readonly,所以此时访问不了,会报错

4、function函数

function函数是什么:

  • JS 中,函数是一等公民。
  • 函数和其他类型的对象都一样,可以作为参数,可以存入数组,也可以被另外一个函数返回,可以被赋值给另外一个变量
  • 函数主要由两个部分组成:输入(传参)输出(返回结果)

我们来看个例子:

function add(x: number, y: number, z?: number): number
    if(typeof z === 'number')
        return x + y + z;
    else
        return x + y;
    


let result = add(1, 2, 3);
console.log(result); //6

通过以上函数,我们实现了两个树或者三个树的相加操作。此时,需要我们注意的是,可选参数后面不能再添加不确定参数,否则程序就会发生混乱。比如:

function add(x: number, y: number, z?: number, t: number): number
    if(typeof z === 'number')
        return x + y + z;
    else
        return x + y;
    

以上代码中的 t 是肯定不被允许添加的,因为前面已经有了可选参数 z ,而后面又突然健冒出来个 t ,想想都不太合理。


到这里,假设我们有一个新的变量名,名字叫 add2 。这个时候我们想要给它一个像 add 函数一样的类型。那么该怎么处理呢?

let add2: (x:number, y:number, z?: number) => number = add

注意上面这个箭头 => 不是 ES6 中的箭头函数,而是 ts 中声明函数类型返回值的方法。

上面这个语句中就说明了, add2 返回的值是一个 number 类型数值,并且让它等于 add 函数。同时,要记得的是,在 ts 当中,凡是在 : 后面都是声明在声明类型。


上面这样写好像有点冗余,我们来用 interface 来实现同样的效果。

在第3点的 interface 中我们了解到, interface 是对对象的形状进行描述,但值得注意的是, interface 也可以是对函数的形状进行描述。我们用代码来实现一下。

interface ISum 
    (x: number, y: number, z?: number) : number


let add2: ISum = add

通过以上代码,我们看到,用 interface 来封装一个函数的返回值来行,看起来优雅了不少。这里先体会一下, interface 的强大之处,在后面还会继续讲解。

5、类型推论、联合类型和类型断言

(1)类型推论

有时候我们还没有给一个数据定义类型,就直接给它赋值了。这个时候我们要怎么来判断呢。这个数据的类型呢?

比如:

let str = 123

当出现这样的情况时,编译器会直接给 str 赋上 number 类型。那么此时如果我们想这么干:

let str = 123
str = 'asd' //会报错

结果当然时不行的。当第一次赋值的时候,编译器就已经给 str 一个 number 类型,认定 str 就是 number 类型。而后我们还想要给 str 赋值上一个 string 类型的数据,肯定是会报错的。

(2)联合类型

有时候我们对一个数据的类型不够确定,比如说不知道某一个数据它是 number 类型还是 string 类型。这个时候我们就可以用联合类型来进行一波操作。

let numberOrString: number | string

通过这种方式,我们对我们所定义的属性 numberOrString 进行联合类型操作。

一般情况下,联合类型会结合类型断言来进行使用。接下来我们来讲类型断言

(3)类型断言

1)TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法,而有时候呢,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法。因此我们采用类型断言的方式将其指定为一个类型。(这么做只是先欺骗了 ts ,让其信任我们所指定的类型)

let str = 123
function getLength(input: string | number) : number
    // 用as对input进行类型断言,先给input指定一个类型,后面判断不是再进行转换
    //注意:类型断言只做类型选择,而不做类型转换
    const str = input as string
    console.log(typeof str)
    if(str.length)
        return str.length
    else
        const number = input as number
        return number.toString().length
    

2) 看到这里,已经开始感觉到类型断言的神奇之处。但用上面这种方法感觉好像还有一点点冗余,于是我们引入一个 type guard ,即类型守护。我们来看下实现方式。

let str = 123
function getLength2(input: string | number): number
    if(typeof input === 'string')
        return input.length
    else
        return input.toString().length
    

上面这种方法被称为是 typeof 语法做类型守护。

3) 继续,我们来看另外一种做类型守护的方法, in 语法具体代码如下:

interface Bird 
    fly: Boolean;
    sing: () => ;


interface Dog 
    fly: boolean;
    bark: () => ;


function trainAnimal(animal: Bird | Dog) 
    if('sing' in animal) 
        animal.sing();
    else 
        animal.bark();
    

在上面的 'sing' in animal 中,我们就可以判断到 animal 是一个 Bird 类型,之后进行 else 操作。上面判断已经是 Bird ,那下面的 else 他就会去往 Dog 类型找,因此也就可以找到 bark 方法。

五、Typescript中的类:class

js 中我们用了构造函数和原型链的方式来实现继承,同时在 ES6 中出现了 class 类继承的方法。那在 typescript 中呢,继承的方法又更加丰富了。让我们一起来一探究竟吧!

1、类的定义

我们先来看下类的定义。

(1)类(Class)

类定义了一切事物的抽象特点,包含它的属性和方法。比如:

class Animal
    // 构造函数是实例化执行时候的逻辑
    constructor(name)
        this.name = name
    
    run()
        return `$this.name is running`
    

阅读以上代码我们可以知道,通过 class 可以定义一个

(2)对象(Object)

对象 Object ,就是类的实例举个例子: 🙆‍♂️

我们可以把类 class 比喻成一张蓝图,比如说汽车是一个 class ,那么它就像是一张造汽车的图纸。第二个是 ObjectObject 通过 new 生成,那么前面有了汽车的蓝图,我们现在就可以创造实实在在的汽车了。我们可以说一辆特斯拉是汽车的实例,也可以说宝马是汽车的另外一个实例。

同样我们用上面的例子来做衍生。具体如下:

class Animal
    // 构造函数是实例化执行时候的逻辑
    constructor(name)
        this.name = name
    
    run()
        return `$this.name is running`
    

const snake = new Animal('lily')
console.log(snake.run())

阅读以上代码我们可以知道,我们定义了一个 snake ,这个 snake 继承了 Animal 类,因此它就可以用 Animal 类的属性方法

此时打印结果如下:

(3)面向对象(OOP)的三大特性

面向对象的三大特性分别为:封装继承多态

  • 封装: 指将数据的操作细节隐藏起来,只暴露对外的接口。那这样子的话,对于外界的调用端来说,他们不需要也不可能知道细节,只能通过对外的接口来访问该对象。
  • 继承: 子类可以继承父类,子类除了拥有父类的所有特征外,还会拥有一些更具体的特性
  • 多态: 由继承产生的相关不同的类,对同一个方法可以有不同的响应。比如,猫和狗,他们都可以继承 Animal 类,但是他们分别实现 run() 方法,此时呢,针对某一个实例,我们无需了解它是猫还是狗,这个时候可以直接调用 run() ,程序会自动判断出来,应该如何去执行这个方法。

同样,我们用上面的代码做衍生,来看继承多态是怎么样的。


继承:

class Animal
    // 构造函数是实例化执行时候的逻辑
    constructor(name)
        this.name = name
    
    run()
        return `$this.name is running`
    

const snake = new Animal('lily')
// console.log(snake.run())

class Dog extends Animal
    bark()
        return `$this.name is barking`
    


const xiaoqi = new Dog('xiaoqi')
console.log(xiaoqi.run())
console.log(xiaoqi.bark())

此时打印结果如下:

从上面可以看到, Dog 继承了 Animal 类,此时 Dog 就拥有了 Animal 类的属性和方法。而 xiaoqi 实例化了 Dog ,因此它也拥有 Dog 的属性和方法。


多态:

class Animal
    // 构造函数是实例化执行时候的逻辑
    constructor(name)
        this.name = name
    
    run()
        return `$this.name is running`
    

const snake = new Animal('lily')
// console.log(snake.run())
//-----------------------------------
class Dog extends Animal
    bark()
        return `$this.name is barking`
    


const xiaoqi = new Dog('xiaoqi')
console.log(xiaoqi.run())
console.log(xiaoqi.bark())
//-----------------------------------
class Cat extends Animal
    // 静态方法不需要进行实例化,直接在类上调用即可
    static categories = ['mammal']
    constructor(name)
        super(name)
        console.log(this.name)
    
    run()
        return `Meow, ` + super.run() 
    

const maomao = new Cat('maomao')
console.log(maomao.run())
// 直接访问静态属性
// 为什么要有静态属性?当定义和实例没有太大关系时,可以考虑使用静态方法实现
console.log(Cat.categories)

此时打印结果如下:

阅读代码我们可以发现, xiaoqi 继承了 dogrun() 方法,而 Cat 继承了 Animal 类,但是它对 run() 方法进行了改写,因此最终的 run() 方法为改写后的效果。

所以, maomao 继承了 Cat 类,最后 maomao 调用 run() 方法时,就会调用 Cat 里面改写的 run() 方法,而不是 Animal 类的 run() 方法。

这样, xiaoqimaomao 虽然同样继承自 Animal 类,但他们调用 run() 方法的结果各自相互独立,如此,就实现了多态。

同时,我们还要注意一个点,就是静态属性。大家可以看到上面定义的 categories ,用了 static 来定义它为静态属性。当把变量定义为静态属性时,则当外部需要该静态方法时,不需要进行实例化,之类在类上调用即可。

那么问题来了,我们什么时候才需要有静态属性呢?

其实,当定义的内容和实例没有太大关系时,就可以考虑使用静态方法。比如常量的使用,常量基本是固定的,不会变的,所以我们可以考虑直接使用静态方法来获取它。

2、Typescript中的类

Typescript是通过什么方式来增强类的呢,typescript一般通过以下四种修饰符来增强类:

修饰符含义
public修饰的属性或方法是公有的
private修饰的属性或方法是私有的
protected修饰的属性或方法是受保护的
readonly只能读不能写

有了以上这四种修饰符呢,我们就可以给类的方法和属性进行权限管理。为什么要做权限管理呢?因为总有些内容,我们是不愿意暴露给外部使用的,所以需要进行权限管理。

值得注意的是,对于 protected 这个修饰符来说,只有子类可以访问父类的属性和方法其他实例都不能访问。这其实可以把 protected 这个变量理解为遗产,父类的东西直接给子女继承,其余外部人员一概不能访问。

3、类和接口

(1)解决什么问题

继承存在着这样一个困境,在面向对象的世界中,一个类只能继承另外一个类,有时候同类之间有一些共同的特性,但是使用子类来继承父类又很难完成。于是接口就出现了。

(2)如何解决

类可以使用 implements 来实现接口,怎么做呢?我们可以把这些相同的特性提取成接口,然后用 implements 这个关键字来实现,这样就大大提高了面向对象的灵活性。

(3)举个例子

假如我们现在要让一辆汽车和一部手机来实现打开播放器的功能那么我们会这么实现:

class Car
    switchRadio(trigger: boolean)

    


class CellPhone
    switchRadio(trigger: boolean)

    

但是这样子看起来好像就没有特别雅观。于是我们可以写一个打开播放器的接口,然后用 implements 来实现这个功能。代码如下:

interface Radio
	switchRadio(trigger: boolean): void


class Car implements Radio
    switchRadio(trigger: boolean)

    


class CellPhone implements Radio
    switchRadio(trigger: boolean)

    

这样,就让 CarCellPhone 实现了打开播放器的功能。

接下来,我们继续写一个接口,可以实现检查电池电量的功能。并且让手机不仅可以打开播放器,还可以检查电池电量。代码如下:

interface Radio
    switchRadio(trigger: boolean): void


interface Battery
    checkBatteryStatus(): void

    
class Car implements Radio
    switchRadio(trigger: boolean)

    


class CellPhone implements Radio,Battery
    switchRadio(trigger: boolean)

    
    checkBatteryStatus()
        
    

阅读代码我们可以发现,我们要给继承两个接口 Radio,Battery ,这样看似乎还有点冗余。于是我们可以这样实现:

interface Radio
    switchRadio(trigger: boolean): void


interface RadioWithBattery extends Radio
    checkBatteryStatus(): void


class Car implements Radio
    switchRadio(trigger: boolean以上是关于还不会ts?一文带你打开ts的大门的主要内容,如果未能解决你的问题,请参考以下文章

还不会ts?一文带你打开ts的大门

TS-- 类型推论类型兼容性高级类型

typeScript核心篇——通过TS写es6类型编程:枚举,类型推论,类型兼容性,高级类型

玩转Rabbitmq系列01:一文带你敲响Rabbitmq的大门

面试官最喜欢问的CAS还不会?怎么和他吹牛?!一文带你搞懂CAS

TypeScript,从0到入门带你进入类型的世界