程序员技术栈ts饥人谷技术博客

TS: 类

2019-03-11  本文已影响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)
上一篇下一篇

猜你喜欢

热点阅读