面向对象-1:类的定义
1. 创建资源
开始实验之前,您需要先创建实验相关资源。
在实验室页面,单击创建资源。
(可选)在实验室页面左侧导航栏中,单击云产品资源列表,可查看本次实验资源相关信息(例如IP地址、子用户信息等)。
说明:资源创建过程需要3~5分钟(视资源不同开通时间有所差异,ACK等资源开通时间较长)。完成实验资源的创建后,您可以在云产品资源列表查看已创建的资源信息,例如:子用户名称、子用户密码、AK ID、AK Secret、资源中的项目名称等。
实验环境一旦开始创建则进入计时阶段,建议学员先基本了解实验具体的步骤、目的,真正开始做实验时再进行创建。
资源创建成功,可在左侧的资源卡片中查看相关资源信息以及RAM子账号信息
2. 实验环境操作指南
在本系列实验中,有两种方法可以进行操作。一种是控制台方式,另一种是远程桌面的方式。接下来我们分别介绍这两种操作方式。
控制台方式
使用控制台方式操作实验,上手比较简单,对网络带宽的要求也不高。但是在编辑代码时,需要使用Vim工具进行文本编辑。对于初学者来说使用Vim工具需要熟悉其操作模式。
进入控制台环境
资源创建成功后,我们在右侧选择切换到Web Terminal。
切换到dev用户
本次实验的开发环境部署在dev用户的~/web目录中。因此我们执行这两条命令即可进入实验环境
su dev cd ~/web
Vim工具的用法
vim是一个命令行下的文本编译器。使用vim编辑文件时。我们只需要通过快捷键,而不需要鼠标就可以对文本文件进行编辑。但是这种文本编辑器的用法和常见的图形界面编辑器使用习惯差别较大。接下来我们简单介绍vim的使用。
3.1 进入Vim
在命令行中输入vim即可进入操作界面,这时的Vim没有打开任何文件。
vim
3.2 Vim的三种模式
和普通编译器不同的是,Vim包含了三种操作模式。分别是命令模式,编辑模式,末行模式。接下来我们来简单介绍一下三种模式的用法。
3.3 命令模式
在刚进入界面的时候,vim默认状态为命令模式。这种模式下,无法编辑文件,键盘输入也不会在屏幕上有任何显示,这种情况往往会让使用者感到困惑。这时候我们可以输入大写的ZZ,即可退出Vim。
3.4 编辑模式
我们继续输入vim回到编辑器。在命令模式下我们按下小写的i。此时我们会发现下部的提示已经变成了插入。这表示vim已经进入了编辑模式。
在编辑模式下我们就可以像正常的编辑器一样编辑文件。此处我们输入hello world。
在编辑完成之后,如果我们想退回命令模式,只需要按ECS键即可。按下该按钮之后我们会发现下部的插入提示消失了,这就表示vim回到了命令模式。
3.5 末行模式
回到命令模式之后,我们按下:键,这时下部的提示会变成:,这就表示vim进入了末行模式。在末行模式下,我们同样使用快捷命令操作vim,但是和命令模式不同的是,末行模式中输入的命令会显示的屏幕的最后一行。
接下来我们继续输入w demo.txt并按回车。会提示显示为如下内容。表示上面的内容已经保存到了demo.txt中。
最后我们按:q,该命令也可以退出vim。
4. 编辑index.js文件
4.1 创建新文件
当我们希望编辑特定文件时,我们可以输入vim 文件名。这时候在下部会看到"index.ts" [新]。表示vim自动创建了新文件。
vim index.ts
4.2 复制粘贴
在编辑模式下,vim同样可以使用ctrl+v进行粘贴。我们按i进入编辑模式之后,将下列内容粘贴到vim中。
//vim编辑模式和命令模式的常用命令: //在编辑模式中:可以使用 上 下 左 右 按键移动光标 //在命令模式中:可以使用 h j k l 命令移动光标 //在编辑模式中:可以使用退格或者DEL键删除字符 //在命令模式中:可以使用 x 命令删除字符 //在命令模式中:可以使用 dd 命令删除整行文字(谨慎使用!) //在命令模式中:可以使用 ggdG 命令删除全部文字(谨慎使用!) //在命令模式中:可以使用 u 命令撤销操作 //在命令模式中:可以使用 ctrl + r 命令重做操作。
4.3 文件内容的查找
在命令模式下,除了按:命令之外,还可以使用/命令进入末行模式,通过/进入末行模式之后,输入相关的内容即可进行全文检索。我们在命令模式下输入/dd。
光标就会移动到相关字符上。
4.3 文件的保存和放弃
当我们编辑完成之后,可以在命令模式中通过ZZ命令直接保存并退出编辑器,也可以通过在末行模式中通过:w单独保存文件。如果我们希望通过末行模式保存并退出,可以使用:wq。
但是需要注意的时,如果文件在修改之后没有保存,是无法通过:q退出的。如果编辑过的文件是只读属性也无法通过ZZ退出。这时我们就可以使用:q!命令放弃所有的编辑内容,强制退出vim。
远程桌面方式
使用远程桌面方式进行实验是,可以使用VS Code编译器进行代码的编写和操作,相对控制台方式更加的直观。但是这种方式对网络带宽的要求比较高。
进入网页环境
1.1 进入远程桌面
资源创建成功后,我们在右侧选择切换到远程桌面。
1.2 启动浏览器
在远程桌面页面中,点击Chromium网页浏览器。
登录RAM用户
2.1 打开网页
浏览器启动后会默认打开阿里云的RAM用户登录页。在登录页面中我们点击下一步按钮。
2.2 获取密码
接下来我们需要输入RAM用户的密码。密码显示在实验控制台左侧,我们点击子用户密码右侧的复制按钮。
复制完成后,我们在用户密码框中按Ctrl + V 复制密码,然后点击登录按钮
进入ECS的远程连接。
3.1 进入ECS控制台
登录成功后,页面跳转到控制台,我们在控制台的搜索框中输入ECS,然后点击云服务器ECS进入云服务器控制台。
3.2 进入远程连接界面
在ECS控制台中,我们找到实验创建的ECS,点击右侧的远程连接按钮。
3.3 选择VNC远程连接
在远程连接中选择通过VNC远程连接中的立即登录如果没有显示该选项,则可点击展开其他登录方式按钮。
登录VNC远程连接
4.1 初始化VNC密码
初次登录VNC远程连接时需要先设置VNC密码。点击重置VNC密码按钮。
输入两次新的VNC密码,并点击确认。
4.2 登录VNC
VNC密码设置成功后,输入密码并点击确认。
4.4 进入图形界面
VNC登录成功后会看到实验ECS的登录界面,在登陆界面中我们点击DEV用户图标
4.5 在图形界面中登录
在密码框中输入默认登录密码Dev12345。即可进入图形实验环境
使用VS Code编辑器
登录图形实验环境之后,接下来我们来启动VS Code 编辑器。
5.1 进入应用程序列表
首先我们点击界面的下方的显示应用程序按钮。
5.2 启动VS Code
在应用程序列表中选择最后一页
然后点击VS Code应用图标
5.3 选择文件
在VS Code 编译器中,左边区域为文件列表,可以选择要编辑的文件
5.4 编辑文件
在VS Code中,右上区域为文件编辑区,可以进行文件内容编辑
5.5 操作终端
在右下区域的终端选项卡,为控制台区域,可以进行命令输入执行
3. 类的定义
类(Class)是面向对象编程方式中信息封装的基础。从逻辑上讲类是对现实生活中一类具有共同特征的事物的抽象,类的内部封装了属性和方法。在TypeScript的ES6版本中提供了对类的原生支持。接下来的实验中我们将学习类的用法。
类的属性定义
在自定义接口时我们会发现,我们在接口中只能加入属性或者函数类型的属性,但是无法直接加入函数。这就导致我们必须使用初始化的方法,为每一个对象实例初始化函数属性。因此这种情况下我们可以使用class也就是类的方式同时定义属性和函数方法。
首先我们来编写类的定义,类定义的语法为class 类名{属性名1:属性类型1;...}。如果们直接编译,会发现编译报错信息,提示在类中的属性需要初始化或者在构造函数中初始化。这也是类和接口的重要不同。我们修改index.ts文件为如下内容。
class person { fullname: string; age: number; }
在终端选项卡输入如下命令编译并查看。
tsc
类的构造函数
和interface不同的是,我们在定义类的时候如果类中包含属性,我们就必须创建名为constructor的构造函数。并且在构造函数中必须对所有的属性进行初始化,一般就来讲我们会将需要初始化的属性作为同名参数传入构造函数中。
需要注意的是,当我们在构造函数中为属性赋值,或者引用类中属性时,需要使用this.属性名的方式。我们修改index.ts文件为如下内容。
class person { fullname: string; age: number; constructor(fullname: string, age: number) { this.fullname = fullname; this.age = age; } }
类元素的初始化
构造函数创建完毕之后,我们就可以创建类对象了。创建类对象的时候,需要使用new 类型(构造函数参数)的方式。我们修改index.ts文件为如下内容。
class person { fullname: string; age: number; constructor(fullname: string, age: number) { this.fullname = fullname; this.age = age; } } var p: person = new person("Aliyun", 20); console.log(p)
在终端选项卡输入如下命令编译并查看,我们可以看到类变量创建成功。
tsc && node index.js
4. 类中的成员
类中的原生函数
虽然类的属性成员还是支持Lambda表达式类型的属性,但是我们一般不会这么使用。而是使用类提供了原生的函数成员。类函数成员的语法为函数名(参数1:参数类型1...):返回值类型。原生函数成员中同样可以使用this.属性名的方式来访问类中的属性。
接下来的例子中,我们在类的定义时就实现一个原生函数login,在login中我们读取类中的属性this.fullname,我们修改index.ts文件为如下内容。
class person { fullname: string; age: number; constructor(fullname: string, age: number) { this.fullname = fullname; this.age = age; } login(password: string): boolean { if (password == "PASSWORD") { console.log("Login Success:name=" + this.fullname + ", password=" + password); return true; } else { console.log("Login Fault:name=" + this.fullname + ", password=" + password); return false; } } } var p: person = new person("Aliyun", 20); p.age = 15 p.login("PASS"); p.login("PASSWORD");
在终端选项卡输入如下命令编译并查看,我们可以看到类函数调用成功。
tsc && node index.js
特殊属性
TypeScript中的类同样支持只读,可选和索引签名属性。其语法和interface中一致。需要注意的是,如果属性声明为可选,那么在构造函数中可以省略对该属性的初始化。我们修改index.ts文件为如下内容。
class person { private fullname: string; public readonly age: number; nickname?: string; [p: number]: number; constructor(fullname: string, age: number) { this.fullname = fullname; this.age = age; } } var p: person = new person("Aliyun", 20); p.nickname = "ALI"; p[0] = 100; console.log(p); console.log(p.nickname); console.log(p[0]);
在终端选项卡输入如下命令编译并查看。
tsc && node index.js
成员的权限控制
在面向对象设计中,属性和函数往往分为内部成员和外部成员,内部成员不可被类之外的对象访问。因此TypeScript为类中的成员提供了访问权限控制关键字。包括内部权限关键字private,外部权限关键字public。如果成员没有设置访问权限,则成员默认为public权限。
在下面的例子中我们可以看到只有默认访问权限或者public权限的成员才可以在外部访问。我们修改index.ts文件为如下内容。
class person { private fullname: string; public age: number; nickname: string; constructor(fullname: string, age: number, nickname: string) { this.fullname = fullname; this.age = age; this.nickname = nickname; } } var p: person = new person("Aliyun", 20, "alibaba"); p.nickname = "ALI" p.age = 100 p.fullname = ""
在终端选项卡输入如下命令编译并查看。
tsc
5. 读取存储函数
在上面的小节中我们学习了TypeScript中的特殊属性。当我们希望一个属性在初始化之后只能被读取时,我们可以使用readonly关键字。实际上除了这种方法之外,还有一种常用的方法。也就是我们将属性定位成private,同时提供一个public的访问函数,用来返回他的属性值。这种函数就叫做属性的读取函数。
同样的我们还可以定义一个写入函数,用来给属性赋值,这种函数就叫存储函数。本小节我们就将介绍属性的存取器的用法。
存取函数的编写
在面向对象中,属性的读取函数和存储函数的函数名有约定俗称的命名规范,一般来说在TypeScript中读取函数用get属性名的方式命名,存储函数用set属性名的方式命名。我们修改index.ts文件为如下内容。
interface TName { first: string; last: string; } class person { private name: TName; constructor(name: TName) { this.name = name; } getName(): TName { return this.name } setName(name: TName) { this.name = name; } } var p: person = new person({ first: "Ali", last: "Yun" }) console.log(p) console.log(p.getName());
在终端选项卡输入如下命令编译并查看。
tsc && node index.js
存取函数的作用
和传统的readonly关键字相比,存取函数虽然编写复杂,但是的优点在于可以为属性的存取过程增加判断逻辑,例如属性的格式转换,写入数据检测等逻辑
在下面的例子中,我们name属性的存取函数,在读取该属性的时候,我们将first和last两个子属性拼接在一起,存储函数中,我们将一个字符串根据空格进行拆分,只有能拆分成first和last两部分的字符串才认为是合法的名字,予以保存。
例子编写完毕后,我们进行测试,会发现当传入的字符串参数阿里云中间没有空格时,无法修改属性。只有当参数为阿里 云时,参数修改才能工程。我们修改index.ts文件为如下内容。
interface TName { first: string; last: string; } class person { private name: TName; constructor(name: TName) { this.name = name; } getName(): string { return this.name.first + " " + this.name.last } setName(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this.name = { first: arr[0], last: arr[1] }; } } var p: person = new person({ first: "Ali", last: "Yun" }) console.log(p.getName()); p.setName("阿里云") console.log(p.getName()); p.setName("阿里 云") console.log(p.getName());
在终端选项卡输入如下命令编译并查看。
tsc && node index.js
构造函数中使用存储函数的问题。
在上面的例子中,我们会发现虽然利用存取函数实现了类型转换和参数检查。但是在构造函数中传入的参数还是TName类型。如果我们尝试在构造函数中调用存储函数。我们会发现在编译时,编译器无法判断name属性是否已经在存储函数中进行了初始化。我们修改index.ts文件为如下内容。
interface TName { first: string; last: string; } class person { private name: TName; constructor(name: string) { this.setName(name) } getName(): string { return this.name.first + " " + this.name.last } setName(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this.name = { first: arr[0], last: arr[1] }; } } var p: person = new person("Ali Yun") console.log(p.getName());
在终端选项卡输入如下命令编译并查看。
tsc
初始化的解决方式
当我们需要在构造函数中使用存储函数时,一般来说有几种做法:
构造一个单独的string到TName的转换函数,构造函数和存储函数共同调用这个转换函数。
先为name赋值一个默认值,然后再使用存储函数
为属性使用特殊关键字!。
这三种方法中,转换函数的代码不够简洁,!关键字的用法在后面的实验中我们会学到,因此此处我们使用预先设置默认值的方式。
interface TName { first: string; last: string; } class person { private name: TName; constructor(name: string) { this.name = { first: "", last: "" } this.setName(name) } getName(): string { return this.name.first + " " + this.name.last } setName(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this.name = { first: arr[0], last: arr[1] }; } } var p: person = new person("Ali Yun") console.log(p.getName()); p.setName("阿里云") console.log(p.getName()); p.setName("阿里 云") console.log(p.getName());
在终端选项卡输入如下命令编译并查看。
tsc && node index.js
6. 属性存取器
在上面的小节中,我们学习了读取存取函数的意义以及编写特点。一般来说存取函数在TypeScript中还原生提供了一种特殊的函数属性存取器。本小节中我们将学习该特殊函数的用法。
存取器的格式
当我们编写一个存取器的时候,我们只需要在普通函数前面加上get或者set关键字即可。需要注意的是,get关键字表示读取器函数,不可带参数且必须有返回值。set表示存储器函数,只能带一个参数且不能有返回值。
另外在类的定义中,函数和属性不能重名。因此针对属性存取器,我们常用的编码规范为:存取器函数名就使用属性,不需要添加get或set的前缀。存取器涉及到的属性名,我们在原有的属性名前加_前缀。我们修改index.ts文件为如下内容。
interface TName { first: string; last: string; } class person { private _name: TName; constructor(name: string) { this._name = { first: "", last: "" } } get name(): string { return this._name.first + " " + this._name.last } set name(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this._name = { first: arr[0], last: arr[1] }; } } var p: person = new person("Ali Yun")
属性存取器的调用
属性存取器定义好之后,我们再来看调用方式。在TypeScript中属性存取器的调用采用的是类似于属性赋值的.和=关键字。也就是说对于存取器来说,我们可以将他当作普通的属性值进行访问。除了使用.关键字之外,属性存取器同样支持[]关键字。我们修改index.ts文件为如下内容。
interface TName { first: string; last: string; } class person { private _name: TName; constructor(name: string) { this._name = { first: "", last: "" } this.name = name; } get name(): string { return this._name.first + " " + this._name.last } set name(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this._name = { first: arr[0], last: arr[1] }; } } var p: person = new person("Ali Yun") console.log(p); p.name = "阿里 云" console.log(p.name); p["name"] = "Ali Yun" console.log(p["name"]);
在终端选项卡输入如下命令编译并查看,
tsc && node index.js
存取器的特点
在上一个步骤中,我们学习了属性存取器的用法。接下来我们来看一些存取器编写的特点。在编写存取器的时候get和set函数并不需要成对出现。如果只包含get函数则该属性只能读取,反之只包含set函数则只能存取。
另外存取器函数同样可以设定权限控制,设置为私有的存取器只能在类的内部函数中使用。同时需要注意的是,如果一个属性同时包括了get和set函数,则get和set函数的权限必须一致。我们修改index.ts文件为如下内容。
interface TName { first: string; last: string; } class person { private _name: TName; constructor(name: string) { this._name = { first: "", last: "" } this.name = name; } private get firstName(): string { return this._name.first } public set lastName(last: string) { this._name.last = last } get name() { return this.firstName + " " + this._name.last; } set name(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this._name = { first: arr[0], last: arr[1] }; } } var p: person = new person("Ali Yun") console.log(p); p.lastName = "云" console.log(p.name);
在终端选项卡输入如下命令编译并查看,
tsc && node index.js
存取器的适用范围
实际上除了类之外,在对象和接口中我们同样可以使用属性存取器语法。只不过在对象和接口中使用存取器时,每次都要初始化存取器函数。和类相比使用起来并不便捷。我们修改index.ts文件为如下内容。
interface TName { first: string; _last: string; get last(): string set last(last: string) } class person { private _name: TName; constructor(name: string) { this._name = { first: "", _last: "", get last(): string { return this._last }, set last(last: string) { this._last = last } } this.name = name; } private get firstName(): string { return this._name.first } public set lastName(last: string) { this._name.last = last } get name() { return this.firstName + " " + this._name.last; } set name(name: string) { var arr = name.split(" "); if (arr.length != 2) return; this._name = { first: arr[0], _last: arr[1], get last(): string { return this._last }, set last(last: string) { this._last = last } }; } } var p: person = new person("Ali Yun") console.log(p); p.lastName = "云" console.log(p.name);
在终端选项卡输入如下命令编译并查看,
tsc && node index.js
实验链接:https://developer.aliyun.com/adc/scenario/f2c6fec289e64a06bc74ce674e64cd6a