typeScript(类篇)

介绍

传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行,而不需要等到下个JavaScript版本。

下面看一个使用类的例子:

class Greeter {greeting: string;constructor(message: string) {this.greeting = message;}greet() {return "Hello, " + this.greeting;}
}let greeter = new Greeter("world");

如果你使用过C#或Java,你会对这种语法非常熟悉。 我们声明一个 Greeter类。这个类有3个成员:一个叫做 greeting的属性,一个构造函数和一个 greet方法。

你会注意到,我们在引用任何一个类成员的时候都用了 this。 它表示我们访问的是类的成员。

最后一行,我们使用 new构造了 Greeter类的一个实例。 它会调用之前定义的构造函数,创建一个 Greeter类型的新对象,并执行构造函数初始化它。

继承

在TypeScript里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。

看下面的例子:

class Animal {move(distanceInMeters: number = 0) {console.log(`Animal moved ${distanceInMeters}m.`);}
}class Dog extends Animal {bark() {console.log('Woof! Woof!');}
}const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();

这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里, Dog是一个 派生类,它派生自 Animal 基类,通过 extends关键字。 派生类通常被称作 子类,基类通常被称作 超类

因为 Dog继承了 Animal的功能,因此我们可以创建一个 Dog的实例,它能够 bark()和 move()

下面我们来看个更加复杂的例子。

class Animal {name: string;constructor(theName: string) { this.name = theName; }move(distanceInMeters: number = 0) {console.log(`${this.name} moved ${distanceInMeters}m.`);}
}class Snake extends Animal {constructor(name: string) { super(name); }move(distanceInMeters = 5) {console.log("Slithering...");super.move(distanceInMeters);}
}class Horse extends Animal {constructor(name: string) { super(name); }move(distanceInMeters = 45) {console.log("Galloping...");super.move(distanceInMeters);}
}let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");sam.move();
tom.move(34);

这个例子展示了一些上面没有提到的特性。 这一次,我们使用 extends关键字创建了 Animal的两个子类: Horse和 Snake

与前一个例子的不同点是,派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this的属性之前,我们 一定要调用 super()。 这个是TypeScript强制执行的一条重要规则。

这个例子演示了如何在子类里可以重写父类的方法。 Snake类和 Horse类都创建了 move方法,它们重写了从 Animal继承来的 move方法,使得 move方法根据不同的类而具有不同的功能。 注意,即使 tom被声明为 Animal类型,但因为它的值是 Horse,调用 tom.move(34)时,它会调用 Horse里重写的方法:

Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.

公共,私有与受保护的修饰符

默认为 public

在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 public来做修饰;例如,C#要求必须明确地使用 public指定成员是可见的。 在TypeScript里,成员都默认为 public

你也可以明确的将一个成员标记成 public。 我们可以用下面的方式来重写上面的 Animal类:

class Animal {public name: string;public constructor(theName: string) { this.name = theName; }public move(distanceInMeters: number) {console.log(`${this.name} moved ${distanceInMeters}m.`);}
}

理解 private

当成员被标记成 private时,它就不能在声明它的类的外部访问。比如:

class Animal {private name: string;tconstructor(theName: string) { this.name = theName; }
}new Animal("Cat").name; // 错误: 'name' 是私有的

typeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

然而,当我们比较带有 private或 protected成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected成员也使用这个规则。

下面来看一个例子,更好地说明了这一点:

class Animal {private name: string;constructor(theName: string) { this.name = theName; }
}class Rhino extends Animal {constructor() { super("Rhino"); }
}class Employee {private name: string;constructor(theName: string) { this.name = theName; }
}let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");animal = rhino;
animal = employee; // 错误: Animal 与 Employee 不兼容.

这个例子中有 Animal和 Rhino两个类, Rhino是 Animal类的子类。 还有一个 Employee类,其类型看上去与 Animal是相同的。 我们创建了几个这些类的实例,并相互赋值来看看会发生什么。 因为 Animal和 Rhino共享了来自 Animal里的私有成员定义 private name: string,因此它们是兼容的。 然而 Employee却不是这样。当把 Employee赋值给 Animal的时候,得到一个错误,说它们的类型不兼容。 尽管 Employee里也有一个私有成员 name,但它明显不是 Animal里面定义的那个。

理解 protected

protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。例如:

class Person {protected name: string;constructor(name: string) { this.name = name; }
}class Employee extends Person {private department: string;constructor(name: string, department: string) {super(name)this.department = department;}public getElevatorPitch() {return `Hello, my name is ${this.name} and I work in ${this.department}.`;}
}let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误

注意,我们不能在 Person类外使用 name,但是我们仍然可以通过 Employee类的实例方法访问,因为 Employee是由 Person派生而来的。

构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,

class Person {protected name: string;protected constructor(theName: string) { this.name = theName; }
}// Employee 能够继承 Person
class Employee extends Person {private department: string;constructor(name: string, department: string) {super(name);this.department = department;}public getElevatorPitch() {return `Hello, my name is ${this.name} and I work in ${this.department}.`;}
}let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.

readonly修饰符

你可以使用 readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

class Octopus {readonly name: string;readonly numberOfLegs: number = 8;constructor (theName: string) {this.name = theName;}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

参数属性

在上面的例子中,我们必须在Octopus类里定义一个只读成员 name和一个参数为 theName的构造函数,并且立刻将 theName的值赋给 name,这种情况经常会遇到。 参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前 Octopus类的修改版,使用了参数属性:

class Octopus {readonly numberOfLegs: number = 8;constructor(readonly name: string) {}
}

注意看我们是如何舍弃了 theName,仅在构造函数里使用 readonly name: string参数来创建和初始化 name成员。 我们把声明和赋值合并至一处。

参数属性通过给构造函数参数前面添加一个访问限定符来声明。 使用 private限定一个参数属性会声明并初始化一个私有成员;对于 public和 protected来说也是一样。

存取器

TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

下面来看如何把一个简单的类改写成使用 get和 set。 首先,我们从一个没有使用存取器的例子开始。

class Employee {fullName: string;
}let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {console.log(employee.fullName);
}

我们可以随意的设置 fullName,这是非常方便的,但是这也可能会带来麻烦。

下面这个版本里,我们先检查用户密码是否正确,然后再允许其修改员工信息。 我们把对 fullName的直接访问改成了可以检查密码的 set方法。 我们也加了一个 get方法,让上面的例子仍然可以工作。

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

我们可以修改一下密码,来验证一下存取器是否是工作的。当密码不对时,会提示我们没有权限去修改员工。

对于存取器有下面几点需要注意的:

首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。

静态属性

到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 static定义 origin,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 origin前面加上类名。 如同在实例属性上使用 this.前缀来访问属性一样,这里我们使用 Grid.来访问静态属性。

class Grid {static origin = {x: 0, y: 0};calculateDistanceFromOrigin(point: {x: number; y: number;}) {let xDist = (point.x - Grid.origin.x);let yDist = (point.y - Grid.origin.y);return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;}constructor (public scale: number) { }
}let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scaleconsole.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。

abstract class Animal {abstract makeSound(): void;move(): void {console.log('roaming the earch...');}
}

抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。

abstract class Department {constructor(public name: string) {}printName(): void {console.log('Department name: ' + this.name);}abstract printMeeting(): void; // 必须在派生类中实现
}class AccountingDepartment extends Department {constructor() {super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()}printMeeting(): void {console.log('The Accounting Department meets each Monday at 10am.');}generateReports(): void {console.log('Generating accounting reports...');}
}let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在

高级技巧

把类当做接口使用

如上一节里所讲的,类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

class Point {x: number;y: number;
}interface Point3d extends Point {z: number;
}let point3d: Point3d = {x: 1, y: 2, z: 3};

来源于类 · TypeScript中文网 · TypeScript——JavaScript的超集

下一篇:typeScript(函数篇)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/119.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初阶C语言——指针

Hello,我们又见面了,时间过的好快啊,转眼间也已经写了这么多份博客了,在接下来的一年里,小编也会认真学习的敲代码,我们一起进步,那今天开始讲我们的指针,指针这一章节在C语言的学习…

前端高度汇总

方法说明 原生js jQuery 浏览器窗口可视区域高度 document.documentElement.clientHeight $(window).height() 浏览器窗口可视区域宽度 document.documentElement.clientWidth $(window).width() 文档高度 document.documentElement.scrollHeight $(document).heigh…

【K8S系列】深入解析K8S监控

序言 做一件事并不难,难的是在于坚持。坚持一下也不难,难的是坚持到底。 文章标记颜色说明: 黄色:重要标题红色:用来标记结论绿色:用来标记论点蓝色:用来标记论点 Kubernetes (k8s) 是一个容器编…

微服务实例构建成 docker 镜像实例

🎈 作者:Linux猿 🎈 简介:CSDN博客专家🏆,华为云享专家🏆,Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我,关注我,有问题私聊! &…

使用WiFi测量仪进行机器人定位的粒子过滤器研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

PLSQL Developer怎样查看当前活动会话

点‘工具’-‘会话’: 选择‘Active sessions’: 点击某个会话,可以看到其对应的sql:

Flutter系列文章-Flutter环境搭建和Dart基础

Flutter是Google推出的一个开源的、高性能的移动应用开发框架,可以用一套代码库开发Android和iOS应用。Dart则是Flutter所使用的编程语言。让我们来看看如何搭建Flutter开发环境,并了解Dart语言的基础知识。 一、Flutter环境搭建 1. 安装Flutter SDK …

设计模式之模板模式

1. 模板模式介绍 1、模板模式即模板方法模式自定义了一个操作中的算法骨架,而将步骤延迟到子类中,使得子类可以不改变一个算法的结构,可以自定义该算法的某些特定步骤; 2、父类中提取了公共的部分代码,便于代码复用&am…

常用的DuiLib的消息类型

文章目录 1、常用的DuiLib的消息类型2、定义所有消息类型 1、常用的DuiLib的消息类型 DUI_MSGTYPE_WINDOWINIT: 窗口初始化消息,用于在窗口创建后执行初始化操作。DUI_MSGTYPE_WINDOWINIT是一个消息类型,用于在窗口初始化时发送消息。当窗口…

【ShenYu系列】ShenYu网关条件匹配的设计及原理分析

ShenYu网关中用到了很多有趣的设计,我对其中的条件匹配的实现尤其感兴趣,所以研究一下具体实现的原理。我这边用到的shenyu版本是2.6.0-SNAPSHOT。 应用入口 原理拆解 AbstractShenyuPlugin#execute,获取到SelectorData集合,进行…

MySQL基础篇第3章(基本的SELECT语句)

文章目录 1、SQL概述1.1 SQL背景知识1.2 SQL分类 2、SQL语言的规则与规范2.1 基本规则2.2 SQL大小写规范 (建议遵守)2.3 注释2.4 命名规则2.5 数据导入指令 3、基本的SELECT语句3.0 SELECT...3.1 SELECT...FROM3.2 列的别名3.3 去除重复行3.4 空置参与运…

探究群体智能:使用Python实现粒子群优化(PSO)、萤火虫算法(FA)、布谷鸟搜索(CS)、蚁群优化(ACO)与人工蜂群(ABC)

在本篇文章中,我们将探索几种基于群体智能的优化算法,这些算法模拟了生物群体中出现的协同行为,并用以解决复杂的优化问题。具体来说,我们将探讨以下五种算法:粒子群优化(PSO)、萤火虫算法&…

OpenCV 入门教程: Sobel算子和Scharr算子

OpenCV 入门教程: Sobel 算子和 Scharr 算子 导语一、Sobel 算子二、Scharr 算子三、示例应用3.1 图像边缘检测3.2 边缘增强 总结 导语 在图像处理和计算机视觉领域,边缘检测是一项重要的任务。 Sobel 算子和 Scharr 算子是两种常用的边缘检测算子&…

MOVEit再现新漏洞,多个版本受影响

今年6月,文件共享工具MOVEit Transfer曾曝出SQL 注入漏洞,能让远程攻击者访问其数据库并执行任意代码。最近,MOVEit Transfer 母公司Progress Software又披露了三个新漏洞。 这三个漏洞分别是 CVE-2023-36932、CVE-2023-36933 和 CVE-2023-36…

云事业群CTO线技术晋升考核机试题-分布式专题-C 分布式任务调度

2023年(Q3财年)技术部门CTO线技术人员晋升考核机试题 分布式篇-C 分布式任务调度 *参考答案* 出题人:湖北TL田超凡答案制定:湖北TL田超凡 *****试卷启用前绝密**** 1 传统的定时任务存在那些缺点 答:传统定时任务…

window安装MongoDB

安装直接先去官网下载 Download MongoDB Community Server | MongoDB 安装后如下,我们直接双击运行, 这里记得选下面(可以自己选择安装盘符位置),上面第一个会自动帮你安装到C盘,然后选择下一步 ,这里勾选就会选择去自…

win10电脑出现网络问题时,如何解决?

我们的Windows可能会出现各种网络连接问题: 尝试连接Wi-Fi网络时出现错误:Windows无法连接到此网络;可以通过Wifi访问互联网,但通过电缆访问以太网却无法正常工作;尝试通过电缆连接互联网时出现错误: Wind…

MATLAB 之 文件操作

这里写目录标题 一、文件操作1. 文件打开与关闭1.1 fopen 函数1.2 fclose 函数 2. 文件的读写2.1 fscanf 函数2.2 fprintf 函数2.3 fgetl 与 fgets函数2.4 textscan 函数2.5 fread 函数2.6 fwrite 函数 3. 数据文件定位3.1 fseek 函数3.2 ftell 函数3.3 feof 函数3.4 ferror 函…

如何使用chatgpt写文章

最近,我身边的朋友都在说ChatGPT,说它是写作的神器,能够帮助他们创作出优质的文章。也许有些人会感到迷惑,什么是ChatGPT?如何使用它来写文章呢? 今天,我将为你揭秘一切!不仅提供详…

NXP i.MX 6ULL工业开发板硬件说明书( ARM Cortex-A7,主频792MHz)

前 言 本文档主要介绍TLIMX6U-EVM评估板硬件接口资源以及设计注意事项等内容。 创龙科技TLIMX6U-EVM是一款基于NXP i.MX 6ULL的ARM Cortex-A7高性能低功耗处理器设计的评估板,由核心板和评估底板组成。核心板经过专业的PCB Layout和高低温测试验证,稳…