在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。

一、类的属性与方法

1.1 类的成员属性和静态属性

在 TypeScript 中,我们可以通过 class 关键字来定义一个类:

class Person {name: string;               // 成员属性constructor(name: string) { // 类的构造函数this.name = name;}
}

在以上代码中,我们使用 class 关键字定义了一个 Person 类,该类含有一个名为 name 的成员属性。其实 TypeScript 中的类是一个语法糖(所谓的语法糖就是在之前的某个语法的基础上改变了一种写法,实现的功能相同,但是写法不同了,主要是为了让开发人员在使用过程中更方便易懂。),若设置编译目标为 ES5 将会产生以下代码:

"use strict";
var Person = /** @class */ (function () {function Person(name) {this.name = name;}return Person;
}());

类除了可以定义成员属性外,还可以通过 static 关键字定义静态属性:

class Person {static cid: string = "exe";name: string; // 成员属性constructor(name: string) { // 类的构造函数this.name = name;}
}

那么成员属性与静态属性有什么区别呢?在回答这个问题之前,我们先来看一下编译生成的 ES5 代码:

"use strict";
var Person = /** @class */ (function () {function Person(name) {this.name = name;}Person.cid = "exe";return Person;
}());

观察以上代码可知,成员属性是定义在类的实例上,而静态属性是定义在构造函数上。

1.2 类的成员方法和静态方法

在 TS 类中,我们不仅可以定义成员属性和静态属性,还可以定义成员方法和静态方法,具体如下所示:

class Person {static cid: string = "exe";name: string; // 成员属性static printCid() { // 定义静态方法console.log(Person.cid);  }constructor(name: string) { // 类的构造函数this.name = name;}say(words: string) :void { // 定义成员方法console.log(`${this.name} says:${words}`);  }
}

那么成员方法与静态方法有什么区别呢?同样,在回答这个问题之前,我们先来看一下编译生成的 ES5 代码:

"use strict";
var Person = /** @class */ (function () {function Person(name) {this.name = name;}Person.printCid = function () {console.log(Person.cid);};Person.prototype.say = function (words) {console.log(this.name + " says\uFF1A" + words);};Person.cid = "exe";return Person;
}());

由以上代码可知,成员方法会被添加到构造函数的原型对象上,而静态方法会被添加到构造函数上。

1.3 类成员方法重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 在定义类的成员方法时,我们也可以对成员方法进行重载:

class Person {constructor(public name: string) {}say(): void; say(words: string): void;say(words?: string) :void { // 方法重载if(typeof words === "string") {console.log(`${this.name} says:${words}`);  } else {console.log(`${this.name} says:Nothing`);  }}
}let p1 = new Person("Semlinker");
p1.say();
p1.say("Hello TS");

如果想进一步了解函数重载的话,可以继续阅读 是时候表演真正的技术了 - TS 分身之术 这一篇文章。

二、访问器

在 TypeScript 中,我们可以通过 gettersetter 方法来实现数据的封装和有效性校验,防止出现异常数据。

let passcode = "Hello TypeScript";class Employee {private _fullName: string = "";get fullName(): string {return this._fullName;}set fullName(newName: string) {if (passcode && passcode == "Hello TypeScript") {this._fullName = newName;} else {console.log("Error: Unauthorized update of employee!");}}
}let employee = new Employee();
employee.fullName = "Semlinker";

在以上代码中,对于私有的 _fullName 属性,我们通过对外提供 gettersetter 来控制该属性的访问和修改。

三、类的继承

继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。通过类的继承,我们可以实现代码的复用。

继承是一种 is-a 关系:

在 TypeScript 中,我们可以通过 extends 关键字来实现类的继承:

3.1 父类

class Person {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }
}

3.2 子类

class Developer extends Person {constructor(name: string) {super(name);this.say("Learn TypeScript")}
}const p2 = new Developer("semlinker");
// 输出: "semlinker says:Learn TypeScript"

因为 Developer 类继承了 Person 类,所以我们可以在 Developer 类的构造函数中调用 say 方法。需要注意的是,在 TypeScript 中使用 extends 时,只能继承单个类:

class Programmer {}// Classes can only extend a single class.(1174)
class Developer extends Person, Programmer {constructor(name: string) {super(name);this.say("Learn TypeScript")}
}

虽然在 TypeScript 中只允许单继承,但却允许我们实现多个接口。具体的使用示例如下所示:

interface CanSay {say(words: string) :void
}interface CanWalk {walk(): void;
}class Person implements CanSay, CanWalk {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }public walk(): void {console.log(`${this.name} walk with feet`);}
}

此外,除了可以继承具体的实现类之外,在实现继承时,我们还可以继承抽象类。

四、抽象类

使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。 所谓的抽象方法,是指不包含具体实现的方法:

abstract class Person {constructor(public name: string){}abstract say(words: string) :void;
}// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error

抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类。具体如下所示:

class Developer extends Person {constructor(name: string) {super(name);}say(words: string): void {console.log(`${this.name} says ${words}`);}
}const lolo = new Developer("lolo");
lolo.say("I love ts!"); // 输出:lolo says I love ts!

五、类访问修饰符

在 TS 类型中,我们可以使用 publicprotectedprivate 来描述该类属性和方法的可见性。

5.1 public

public 修饰的属性或者方法是公有的,可以在任何地方被访问到,默认所有的属性或者方法都是 public:

class Person {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }
}

5.2 protected

protected 修饰的属性或者方法是受保护的,它和 private 类似,不同的地方是 protected 成员在派生类中仍然可以访问。

class Person {constructor(public name: string) {}public say(words: string) :void {console.log(`${this.name} says:${words}`);  }protected getClassName() {return "Person";}
}const p1 = new Person("lolo");
p1.say("Learn TypeScript"); // Ok
// Property 'getClassName' is protected and only accessible within class 'Person' and its subclasses.
p1.getClassName() // Error

由以上错误信息可知,使用 protected 修饰符修饰的方法,只能在当前类或它的子类中使用。

class Developer extends Person {constructor(name: string) {super(name);console.log(`Base Class:${this.getClassName()}`);}
}const p2 = new Developer("semlinker"); // 输出:"Base Class:Person"

5.3 private

private 修饰的属性或者方法是私有的,只能在类的内部进行访问。

class Person {constructor(private id: number, public name: string) {}
}const p1 = new Person(28, "lolo");
// Property 'id' is private and only accessible within class 'Person'.(2341)
p1.id // Error
p1.name // OK

由以上错误信息可知,使用 private 修饰符修饰的属性,只能在当前类内部访问。但真的是这样么?其实这只是 TS 类型检查器给我们的提示,在运行时我们还是可以访问 Person 实例的 id 属性。不相信的话,我们来看一下编译生成的 ES5 代码:

"use strict";
var Person = /** @class */ (function () {function Person(id, name) {this.id = id;this.name = name;}return Person;
}());
var p1 = new Person(28, "lolo");

5.4 私有字段

针对上面的问题,TypeScript 团队在 3.8 版本就开始支持 ECMAScript 私有字段,使用方式如下:

class Person {#name: string;constructor(name: string) {this.#name = name;}
}let semlinker = new Person("semlinker");
// Property '#name' is not accessible outside class 'Person' because it has a private identifier.
semlinker.#name // Error

那么 ECMAScript 私有字段private 修饰符相比,有什么特别之处么?这里我们来看一下编译生成的 ES2015 代码:

"use strict";
var __classPrivateFieldSet = // 省略相关代码
var _Person_name;
class Person {constructor(name) {_Person_name.set(this, void 0);__classPrivateFieldSet(this, _Person_name, name, "f");}
}_Person_name = new WeakMap();
let semlinker = new Person("Semlinker");

观察以上的结果可知,在处理私有字段时使用到了 ES2015 新增的 WeakMap 数据类型,如果你对 WeakMap 还不了解的话,可以阅读 你不知道的 WeakMap 这篇文章。下面我们来总结一下,私有字段与常规属性(甚至使用 private 修饰符声明的属性)不同之处:

  • 私有字段以 # 字符开头,有时我们称之为私有名称;

  • 每个私有字段名称都唯一地限定于其包含的类;

  • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);

  • 私有字段不能在包含的类之外访问,甚至不能被检测到。

六、类表达式

TypeScript 1.6 添加了对 ES6 类表达式的支持。类表达式是用来定义类的一种语法。和函数表达式相同的一点是,类表达式可以是命名也可以是匿名的。如果是命名类表达式,这个名字只能在类体内部才能访问到。

类表达式的语法如下所示([] 方括号表示是可选的):

const MyClass = class [className] [extends] {// class body
};

基于类表达式的语法,我们可以定义一个 Point 类:

let Point = class {constructor(public x: number, public y: number) {}public length() {return Math.sqrt(this.x * this.x + this.y * this.y);}
}let p = new Point(3, 4);
console.log(p.length()); // 输出:5

需要注意在使用类表达式定义类的时候,我们也可以使用 extends 关键字。篇幅有限,这里就不展开介绍了,感兴趣的小伙伴可以自行测试一下。

七、泛型类

在类中使用泛型也很简单,我们只需要在类名后面,使用 <T, ...> 的语法定义任意多个类型变量,具体示例如下:

class Person<T> {constructor(public cid: T, public name: string) {}
}let p1 = new Person<number>(28, "Lolo");
let p2 = new Person<string>("exe", "Semlinker");

接下来我们以实例化 p1 为例,来分析一下其处理过程:

  • 在实例化 Person 对象时,我们传入 number 类型和相应的构造参数;

  • 之后在 Person 类中,类型变量 T 的值变成 number 类型;

  • 最后构造函数 cid 的参数类型也会变成 number 类型。

相信看到这里一些读者会有疑问,我们什么时候需要使用泛型呢?通常在决定是否使用泛型时,我们有以下两个参考标准:

  • 当你的函数、接口或类将处理多种数据类型时;

  • 当函数、接口或类在多个地方使用该数据类型时。

八、构造签名

在 TypeScript 接口中,你可以使用 new 关键字来描述一个构造函数:

interface Point {new (x: number, y: number): Point;
}

以上接口中的 new (x: number, y: number) 我们称之为构造签名,其语法如下:

ConstructSignature:newTypeParametersopt(ParameterListopt)TypeAnnotationopt

在上述的构造签名中,TypeParametersoptParameterListoptTypeAnnotationopt 分别表示:可选的类型参数、可选的参数列表和可选的类型注解。那么了解构造签名有什么用呢?这里我们先来看个例子:

interface Point {new (x: number, y: number): Point;x: number;y: number;
}class Point2D implements Point {readonly x: number;readonly y: number;constructor(x: number, y: number) {this.x = x;this.y = y;}
}const point: Point = new Point2D(1, 2); // Error

对于以上的代码,TypeScript 编译器(v4.4.3)会提示以下错误信息:

Type 'Point2D' is not assignable to type 'Point'.
Type 'Point2D' provides no match for the signature 'new (x: number, y: number): Point'.

要解决这个问题,我们就需要把对前面定义的 Point 接口进行分离:

interface Point {x: number;y: number;
}interface PointConstructor {new (x: number, y: number): Point;
}

完成接口拆分之后,除了前面已经定义的 Point2D 类之外,我们又定义了一个 newPoint 工厂函数,该函数用于根据传入的 PointConstructor 类型的构造函数,来创建对应的 Point 对象。

class Point2D implements Point {readonly x: number;readonly y: number;constructor(x: number, y: number) {this.x = x;this.y = y;}
}function newPoint( // 工厂方法pointConstructor: PointConstructor,x: number,y: number
): Point {return new pointConstructor(x, y);
}const point: Point = newPoint(Point2D, 1, 2);

九、抽象构造签名

在 TypeScript 4.2 版本中引入了抽象构造签名,用于解决以下的问题:

type ConstructorFunction = new (...args: any[]) => any;abstract class Utilities {}// Type 'typeof Utilities' is not assignable to type 'ConstructorFunction'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
let UtilityClass: ConstructorFunction = Utilities; // Error.

由以上的错误信息可知,我们不能把抽象构造器类型分配给非抽象的构造器类型。针对这个问题,我们需要使用 abstract 修饰符:

declare type ConstructorFunction = abstract new (...args: any[]) => any;

需要注意的是,对于抽象构造器类型,我们也可以传入具体的实现类:

declare type ConstructorFunction = abstract new (...args: any[]) => any;abstract class Utilities {}
class UtilitiesConcrete extends Utilities {}let UtilityClass: ConstructorFunction = Utilities; // Ok
let UtilityClass1: ConstructorFunction = UtilitiesConcrete; // Ok

而对于 TypeScript 4.2 以下的版本,我们可以通过以下方式来解决上面的问题:

type Constructor<T> = Function & { prototype: T }abstract class Utilities {}class UtilitiesConcrete extends Utilities {}let UtilityClass: Constructor<Utilities> = Utilities;
let UtilityClass1: Constructor<UtilitiesConcrete> = UtilitiesConcrete;

介绍完抽象构造签名,最后我们来简单介绍一下 class type 与 typeof class type 的区别。

十、class type 与 typeof class type

class Person {static cid: string = "exe";name: string; // 成员属性static printCid() { // 定义静态方法console.log(Person.cid);  }constructor(name: string) { // 类的构造函数this.name = name;}say(words: string) :void { // 定义成员方法console.log(`${this.name} says:${words}`);  }
}// Property 'say' is missing in type 'typeof Person' but required in type 'Person'.
let p1: Person = Person; // Error
let p2: Person = new Person("Semlinker"); // Ok// Type 'Person' is missing the following properties from type 'typeof Person': prototype, cid, printCid
let p3: typeof Person = new Person("Lolo"); // Error
let p4: typeof Person = Person; // Ok

通过观察以上的代码,我们可以得出以下结论:

  • 当使用 Person 类作为类型时,可以约束变量的值必须为 Person 类的实例;

  • 当使用 typeof Person 作为类型时,可以约束变量的值必须包含该类上的静态属性和方法。

此外,需要注意的是 TypeScript 使用的是 结构化 类型系统,与 Java/C++ 所采用的 名义化 类型系统是不一样的,所以以下代码在 TS 中是可以正常运行的:

class Person {constructor(public name: string) {}
}class SuperMan {constructor(public name: string) {}
}let p1: SuperMan = new Person("Semlinker"); // Ok

好的,在日常工作中,TypeScript 类比较常见的知识,就介绍到这里,感谢阿宝哥的分享!

十一、参考资源

  • TypeScript 1.6

  • how-to-use-classes-in-typescript

  • Creator游戏开发教程PDF免费下载

  • 编写高效 TS 的一些建议

  • JavaScript 内存详解 & 分析指南

  • Webview 性能优化应该关注哪些指标?

  • 了不起的 tsconfig.json 指南

TS 类的这10个知识点你掌握了吗?相关推荐

  1. 高级 Java 必须突破的 10 个知识点

    转载自 高级 Java 必须突破的 10 个知识点! 工作多少年了,还在传统公司写if / for 等简单的代码?那你就真的要被社会淘汰了,工作多年其实你与初级工程师又有多少区别呢?那么作为一个高级J ...

  2. python为啥这么受欢迎_Python为什么这么受欢迎?原因竟在于它的10个知识点

    Python被誉为全世界高效的编程语言,同时也被称作是"胶水语言".Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.设计具有很强的可读性,相比其他语言 ...

  3. 二级VB培训笔记10:知识点串讲

    二级VB培训笔记10:知识点串讲 一.对象概述 1.三大特性:封装性.继承性.多态性 参看<VB讲课笔记1202. 二级公共基础 - 程序设计基础> 2.对象的属性 了解VB对象的常用属性 ...

  4. 假设系统中共有5个{P0,P1,P2,P3,P4}和A,B,C三类资源;A类资源共有10个,B类资源共有5个,C类资源共有7个。在时刻T0,系统资源分配情况如下表8-14所示。

    银行家算法应用 题目: 假设系统中共有5个{P0,P1,P2,P3,P4}和A,B,C三类资源:A类资源共有10个,B类资源共有5个,C类资源共有7个.在时刻T0,系统资源分配情况如下表8-14所示. ...

  5. 6-1 Point类的运算 (10 分)

    6-1 Point类的运算 (10 分) 定义Point类,有坐标x,y两个私有成员变量;对Point类重载"+"(相加)."-"(相减)和"==&q ...

  6. typescript学习之路(四) —— ts类的继承(包含es5以及es6的类继承)

    上一文已经写了es5,es6等类的定义,所以本章主要写es5和es6的继承,由于es6的继承和ts的继承如出一辙,只是加了类型定义而已,所以ts的继承稍微写下,不会太详细. 文章目录 es5继承 原型 ...

  7. 【本人秃顶程序员】高级 Java 必须突破的 10 个知识点!

    ←←←←←←←←←←←← 快!点关注!!! 工作多少年了,还在传统公司写if / for 等简单的代码?那你就真的要被社会淘汰了,工作多年其实你与初级工程师又有多少区别呢?那么作为一个高级Java攻城 ...

  8. 【TS】1010- 细数 10 个 TypeScript 奇怪的符号

    TypeScript 是一种由微软开发的自由和开源的编程语言.它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程. 本文阿宝哥将分享这些年在学习 T ...

  9. (转载)类欧几里得(知识点整理+板子总结)

    思路来源 类欧几里得算法_wwx233的博客-CSDN博客 类欧几里得算法 - cyz666 - 博客园 类欧几里得算法总结_DZYO的博客-CSDN博客(证明清楚详细) 数数[数位DP||类欧]_以 ...

最新文章

  1. 针对IE6\7\8\9\10浏览器的CSS hack大全详解
  2. python读音有道词典-利用python实现命令行有道词典的方法示例
  3. 模块化封装 --- 双ToKen 实现免登录步骤详解
  4. 组件局域网中的无集线器、Windows XP、Windows 7、Windows 8的对等网
  5. 用于基于SWT的应用程序的RichText编辑器组件
  6. win10电脑桌面透明便签_在win10电脑桌面上使用工作跟进提醒办公软件可用哪个便签软件?...
  7. Java反射设置list的属性值_利用java反射比较两个实体有哪些属性值不一样
  8. 谈判中,要学会“留一手”
  9. Power BI Desktop报告中的Web URL配置
  10. 【机器学习】CART决策树原理及python实现
  11. 梦幻西游手游海外服务器维护,梦幻西游手游维护 新神器任务轩辕丘之祸开启...
  12. 离散数学---序偶,笛卡尔积,自反,闭包
  13. unity交通仿真_自动驾驶仿真该怎么玩
  14. 使用DAX(Power BI)的移动平均线
  15. 干货!mysql初始密码
  16. 3G杀手应用还是全方位轰炸?
  17. 火狐浏览器Firefox Firebug使用方法
  18. Re:从零开始的DS学习之查找算法
  19. numpy基础—numpy的轴
  20. 天天链n1 与电脑连接Samba win10 教程

热门文章

  1. 华为否认因“子承父业”挤走孙亚芳( 转载)
  2. unreal 虚幻引擎学习资料
  3. 888道Java高级面试题,java邮件全部解析eml
  4. 微信隐藏功能:微信提现怎么免手续费?勤俭持家的福音,2步做到
  5. 神经网络一般架构(BP)
  6. 上海市高校大学生程序设计邀请赛 C:小花梨判连通
  7. 关于ViewGroup和View的事件分发
  8. 3.26 haas506 2.0开发教程-example- 简易相机-串口控制ESP32-CAM OV2640拍照
  9. IE6绿色版本-IE Developer Toolbar可正常使用
  10. 机械和计算机分数线,东北大学通信,冶金,计算机,材料,机械历年分数线