TS: 类

作者: 写代码的海怪 | 来源:发表于2019-03-11 12:30 被阅读35次

TS 的类其实和 ES6 里的类差不多,只不过 TS 加多了一些功能。这篇文章会介绍 TS 类的常用功能与接口的对比,以及抽象类。

入门

还是先从基础(ES6 自带)的语法讲起吧,假设现在我们要定义 Github 的 GithubRepository 类。

class GithubRepository {
    name: string
    commits: number

    constructor(name: string) {
        this.name = name
        this.commits = 0
    }

    remove():void {
        console.log('Delete this repo')
    }

    rename(name: string):void {
        this.name = name
    }
}

let repo = new GithubRepository('My Repo')

GithubRepository 有仓库名字 (name),commit 的次数 (commits),还有删除仓库 (delete) 和重命名 (rename) 两个方法。这就是一个完整类的定义。

其中要注意的是一定要有 constructor 构造器,这是在 let repo = new GithubRepository('My Repo') 的时候用来创建临时对象的,在创建之后再将对象的内存地址赋值给 repo 的,所以无论要不要初始化类里的变量都要写 constructor。

继承

在 ES6 之前 JS 已经可以通过原型链实现类的继承功能了,ES6 其实加了个语法糖,而 TS 里的类继承和 ES6 是一样的。

现在微软收购了 Github 了嘛,那就假设他们家的仓库继承了 Github 的 GithubRepository 类吧。

class GithubRepository {
    public name: string
    public commits: number

    constructor(name: string) {
        this.name = name
        this.commits = 0
    }

    remove():void {
        console.log('Delete this repo')
    }

    rename(name: string):void {
        this.name = name
    }
}

class MicrosoftRepository extends Repository {
    public logo: string = 'Microsoft'
    constructor(name: string) {
        super(name)
    }
}

let repo = new MicrosoftRepository('My Repo')

现在假设微软的生成的 MicrosoftRepository 都要加上微软的 logo,所以 logo 放在 MicrosoftRepository 里。而在 constructor 里要调用 super 方法,还要将 name 传过去,作用相当于调用了 GithubRepository 类的 constructor。

现在变量 repo 就可以使用 GithubRepository 类里的方法,同时也具有 logo 属性了。

console.log(repo.name) // "My Repo"

repo.remove() // "Delete this repo"

repo.rename('Your Repo')
console.log(repo.name) // "Your Repo"

console.log(repo.logo) // "Microsoft"

作用域

public

现在我们定义 GithubRepository 里 namecommits 都是可以被外界访问的,所以这两个默认的作用域是 public,也就说可以写成这样

class GithubRepository {
    public name: string
    public commits: number
    ...
}

let repo = new GithubRepository('My Repo')

console.log(repo.name) // 可以访问,"My Repo"
console.log(repo.commits) // 可以访问,0

private

假设现在有个变量 githubLogo 只能只属于 GithubRepoistory,而外界不能访问,当然微软的类也是不能访问的,这就要设置成 private 了。

class GithubRepository {
    ...
    private githubLogo: string 
    ...
}

class MicrosoftRepository extends GithubRepository {
    public logo: string = 'Microsoft'
    constructor(name: string) {
        super(name)
        console.log(this.commits)    // 0
        console.log(this.githubLogo) // 不能访问,报错
    }
}

let repo = new GithubRepository('My Repo')

console.log(repo.githubLogo) // 出错,不能访问 githubLogo

protected

protected 的作用域就是只能在“本家族”里才能访问,别人都不能访问,有点像“家传秘方”的意思。

假设 Github 的 GithubRepository 有家传的推荐算法 recommend,微软不想自己实现推荐算法,所以只好继承 GithubRepository 祖传的推荐算法喽。

class GithubRepository {
    ...
    protected recommend(): void {
        console.log('Recommend...')
    }
}

class MicrosoftRepository extends GithubRepository {
    public logo: string = 'Microsoft'
    constructor(name: string) {
        super(name)
        this.recommend() // "Recommend..."
    }
}

let repo = new MicrosoftRepository('My Repo')
repo.recommend() // 不能访问 recommend,报错

静态属性

静态属性(方法)用关键字 static 来表示。静态属性可以不用创建对象就可以访问该属性/调用该方法。

假设现在 GithubRepository 有一个方法是获取该仓库下截量的方法,用静态方法可以写成这样

class GithubRepository {
    ...
    static getDownload(name: string): void {
        console.log(`Repo ${name} download/month is ....`)
    }
}

如果我们要查某个仓库的每月下载量就可以直接调用 getDownload 方法即可

GithubRepository.getDownload('MyRepo')

而不是创建一个 GithubRepository 去调用

(new GithubRepository()).getDownload('MyRepo')

静态属性类似,我们可以给 GithubRepository 加一个官网链接,这个链接变量设置为静态属性。

class GithubRepository {
    ...
    public static url: string = 'https://github.com'
    ...
}

console.log(GithubRepository.url) // "https://github.com"

setter 与 getter

刚刚说过可以用 private 关键字使得某些属性不对外公开,这样我们就可以隐藏一些功能的实现了。比如说对不同浏览器的兼容等。

回到我们的例子,这个 GithubRepository 要对 Firefox,Chrome,IE 进行兼容,不同浏览器要去计算对应 Logo 的横坐标 X,这就可以使用 setter 与 getter 来完成了。

class GithubRepository {
    ...
    private _logoPositionX: number = 0

    set logoPositionX(rawX: number) {
        browser = getBrowserName()
        if (browser === 'IE') {
            this._logoPositionX = rawX + 1
        }
        else if (browser = 'Chrome') {
            this._logoPositionX = rawX + 2
        }
        else if (browser = 'Firefox') {
            this._logoPositionX = rawX + 3
        }
    }
    get logoPositionX(): number {
        return this._logoPositionX
    }
    ...
}

let repo = new MicrosoftRepository('My Repo')
repo.logoPositionX = 2
console.log(repo.logoPositionX) // "4"

上面的代码就将 _logoPositionX 隐藏了,每次设置新的位置时都会根据当前的浏览器进行再将计算,这就完成了浏览器的兼容,而这个兼容的操作外面是不知道的。外界只需要设置位置,和获取位置就可以了。

类与接口

其实这两个东西都是对创建对象的一种约束,不同的是类像是一个工厂,里面有很多功能,如设置属性的作用域,初始化对象等。接口更像说明书,只是说明这个对象应该有什么属性/方法,就没了。

使用代码可以看出他们有很大的不同。

interface Human {
    name: string
    gender: string
}

let jack:Human = {
    name: 'Jack',
    age: 18
}

下面是类的声明

class Human {
    name: string
    gender: string
    constructor(name, gender) {
        this.name = name
        this.gender = gender
    }
}

let jack = new Human('Jack', 18)

从上面可以看到接口的写法完全可以用类来替代,但是写类麻烦。简单来说两者的区别就是:

  • 接口是类的低配版
  • 类是接口的调配版

抽象类

说完接口与类的区别,我们来看看抽象类。抽象类的用法是在类的基础上可以不实现一些方法,而让子类去实现。

回到我们的例子,假设 Github 本来一直想实现一套仓库排名的算法,直到微软收购了还没有实现,所以这个重任就交给微软做了。

abstract class GithubRepository {
    ...
    // 声明抽象方法
    abstract sort(): void
}

class MicrosoftRepository extends GithubRepository {
    ...
    // 实现抽象方法
    sort(): void {
        console.log('Sorting...')
    }
}

let repo = new MicrosoftRepository('My Repo')
repo.sort() // "Sorting"

这里 GithubRepository 变成了抽象类,前面加 abstract,里面就有一个还没实现的 sort 方法,所以前面也要加 abstract。到了 MicrosoftRepository,他就一定要去实现 sort 方法了。

这里要注意的点是抽象类不能用来创建实例,想想看,如果可以创建实例,那未实现的方法调用怎么办呢?所以一定要有一个子类去实现那些未实现的方法,再用这个子类去创建实例。所以抽象类一般都作为“父亲类”,术语叫基类。他的功能是比一般的类要多的(可以声明未完成的方法)。

就像以前总有科学家提出 XXX 猜想,但就是自己不去实现或者自己不能实现,反而让那些苦逼大学生去实现。

抽象类与接口

这个抽象类怎么看起来和接口差不多呀。是差不多,但是又不能完全一样。

就像刚刚说的接口只是一份说明书,而抽象类就像工厂里的科学家,他提出很多猜想,同时也完成了很多实现,别的工厂(子类)就用继承他的思想去做自己的产品(创建实例)。

interface Human {
    name: string
    age: string
}

let jack = {
    name: 'Jack',
    age: 18
}

下面再看看抽象类的实现。

abstract class God {
    constructor() { }
    abstract createHuman(): void
}

abstract class Woman extends God {
    createHuman():void {
        console.log('XXOO') // :)
    }
}

当然还能这么写

abstract class X {
    name: string
    gender: string
}

class Human extends X {
    constructor(name: string, gender: string) {
        super()
        this.name = name
        this.gender = gender
    }
}

let jack = new Human('Jack', 'Male')

console.log(jack.name, jack.gender)

相关文章

  • 8、TypeScript 接口继承接口,类实现多接口

    1、ts类中只能继承一个父类2、ts类中可以实现多少接口,使用(,)号分隔3、ts接口中可以继承多个接口,使用(,...

  • TS类

    类 文档类就是用来创造对象的东西。有一些语言(如 Java,存疑)创建对象必须先声明一个类,而有的语言(JS)则不...

  • 【TS】类

    定义 class 继承 extends 不同的叫法:PERSON:父类、基类、超类STUENT:子类、派生类 属性...

  • TS: 类

    TS 的类其实和 ES6 里的类差不多,只不过 TS 加多了一些功能。这篇文章会介绍 TS 类的常用功能与接口的对...

  • 类、继承、多态

    ts中定义类 ts中继承----父类方法与子类方法 类里面的修饰符 public :公有 在当前类里面、 子类 、...

  • typeScript语法

    ts类型 ts联合类型使用或 ts定义任意类型any ts定义函数返回值的类型 ts中的类定义 interface接口

  • 面向对象特性

    1、类(Class)类是ts的核心,使用ts开发时,大部分代码是写在类里面的。类的定义、构造函数、类的继承;类的定...

  • TS高级类型:Extract 与 Exclude

    Extract 是TS提供的一个TS高级type类型【简称TS高级类型】 Extract 用于类 Extract ...

  • umijs@use-request源码解读

    一、了解ts基本语法 涉及ts的变量声明、接口、类、函数、泛型等 ts语法知识[https://typescrip...

  • ts的学习

    安装全局ts tsconfig.json文件的配置 ts基础类型 class类的练习

网友评论

    本文标题:TS: 类

    本文链接:https://www.haomeiwen.com/subject/xtsvpqtx.html