TS: 类

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)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 类 文档类就是用来创造对象的东西。有一些语言(如 Java,存疑)创建对象必须先声明一个类,而有的语言(JS)则不...
    郑无穷大阅读 1,449评论 0 0
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 二五班2019.02.19 星期二 天气多云 元宵佳节,公司给大家调休了,很人性化的一面,团圆的节日,...
    小象妈咪阅读 342评论 0 6
  • 最近这周主要做了两件事, 一是应朋友邀请到忻州繁峙过了元宵节, 二是复习了一遍理财训练营课程,整理了我投资的指数基...
    lv老师lv阅读 237评论 0 2