如果你不是做前端的,那么学鸿蒙之前先了解ts,或者说先复习,学会ts就是学会鸿蒙arkts。
[toc]
2.2 TypeScript 快速⼊⻔
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准 TypeScript 由微软开发的自由和开源的编程语言。 TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
语言特性
TypeScript 是一种给 JavaScript 添加特性的语言扩展。增加的功能包括:
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口
- 枚举
- Mixin
- 泛型编程
- 名字空间
- 元组
- Await 以下功能是从 ECMA 2015 反向移植而来:
- 类
- 模块
- lambda 函数的箭头语法
- 可选参数以及默认参数
JavaScript 与 TypeScript 的区别 TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。 TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
学习网址:www.runoob.com/typescript/…
2.2.1. 运⾏环境说明
2.2.1.1. 线上Playgroun
TypeScript提供了⼀个线上的 Playground 供练习使⽤,地址为 www.typescriptlang.org/zh/play
2.2.1.2. 本地运⾏环境
除去线上的运⾏环境,我们也可以在本地搭建⼀个 TS 的运⾏环境。 1.安装 VSCode 编辑器 VSCode是⼀款轻量级、开源且功能丰富的集成开发环境(IDE),⽀持多种编程语⾔,具有 强⼤的插件系统。下载地址为:code.visualstudio.com
2.安装Code Runner 插件 Code Runner是⼀款在VSCode中使⽤的插件,它提供了简便的代码执⾏功能,⽀持多种编 程语⾔,使开发者能够快速运⾏和调试代码⽚段。
3.安装ts-node
ts-node是⼀个 TypeScript 的运⾏环境,它允许我们直接运⾏ TypeScript 代码。ts-node 的安装和运⾏依赖于Node.js环境,因此在安装ts-node之前,我们需要准备好Node.js环 境。 Node.js是一个调用内置ApI并且基于Chrome V8引擎的js运行环境,之前自己在本地总结了一些零散的只知识点,今天整合一下发出来。
准备Node.js环境需要完成以下两步操作 安装Node.js nodejs.org/zh-cn/
配置环境变量 然后打开环境变量配置⾯板,按下 Win + R ,唤起运⾏窗⼝,之后运⾏命 令 sysdm.cpl
之后点击⾼级选项卡,并点击环境变量
然后在系统变量中选中 Path ,并点击编辑
之后点击新建,并填⼊Node.js的安装⽬录,完成后点击确定。
在配置完Node.js环境后,便可在终端执⾏以下命令来安装ts-node了
npm install -g ts-node
注:完成后需要重新启动VSCode,另其重新加载环境变量和相关依赖。 4.编写程序并运 在完成上述环境的准备后,就可以编写Typescript程序并运⾏了,具体操作如下 ⾸先在合适的位置创建⼀个⼯程⽬录,例如 D:\workspace\hello-ts ,然后使⽤ VSCode打开⽬录
之后创建Typescript⽂件,点击New File
注意,⽂件的后缀为 .ts
之后就可以编写Typescript代码并运⾏了
2.2.2. 声明
2.2.2.1. 变量声明
2.2.2.2. 常量声明
let ⽤于声明变量,⽽const ⽤于声明常量,两者的区别是变量在赋值后可以修改,⽽常量在赋值后便不能再修改。
const b:number = 300;
2.2.2.3. 类型型推
2.2.3.1. number
number 表示数字,包括整数和浮点数,例如: 101 、-96 、 3.14 、-3.15
let a :number = 101
let b :number = -96
let c :number = 3.14
let d :number = -3.15
2.2.3.2. string
string 表示字符串,例如:你好,小老弟。 、 Hello, little buddy.
let a:string = ' '你好,小老弟。 '
let b:string = 'Hello, little buddy.'
2.2.3.3. boolean
boolean 表示布尔值,可选值为: true 、 false
let isOpen:boolean = true
let isDone:boolean = false
2.2.3.4. 数组
数组类型定义由两部分组成, 元素类型 [] ,例如 number[] 表示数字数组, 示字符串数组,数组类型的变量可由数组字⾯量—— string[] 表 [item1,item2,item3] 进⾏初始化。
let a: number[] = []
let b: string[] = ['你好,小冰老师!', 'Hello, Miss Ice']
2.2.3.5. 对象
在TS中,对象(object)是⼀种⼀系列由属性名称和属性值组成的数据结构,例如 姓名 :'张三', 年龄 :20, 性别 :' 男 ' 。对象类型的声明需要包含所有属性的名称及类型,例如 {name: string, age: number, gender: string} ,对象类型的变量可以通过对象字⾯量 —— {name:' 张三 ', age:20, gender:' 男 '} 进⾏初始化
let person: {name:string, age:number, gender:string} = {name:'张三', age:10, gender:'男'};
2.2.4. 函数
2.2.4.1. 函数声明语
声明函数的基础语法如下
2.2.4.2. 参数详解
2.2.4.2.1. 特殊语法
- 可选参数 可选参数通过参数名后的 ? 进⾏标识,如以下案例中的 gender? 参数
function getPersonInfo(name: string, age: number, gender?: string): string {
if (gender === undefined) {
gender = '未知'
}
return `name:${name},age:${age},gender:${gender}`;
}
let p1 = getPersonInfo('zhagnsan', 10, '男')
let p2 = getPersonInfo('lisi', 15);
console.log(p1);
console.log(p2)
注:调⽤函数时,未传递可选参数,则该参数的值为 undefined 。
- 默认参数 可在函数的参数列表为参数指定默认值,如以下案例中的 数。 gender: string=' 未知 '
function getPersonInfo(name: string, age: number, gender: string='未知'): string { return `name:${name},age:${age},gender:${gender}`;
}
let p1 = getPersonInfo('zhagnsan', 10, ' 男 ')
let p2 = getPersonInfo('lisi', 15);
console.log(p1);
console.log(p2)
- 联合类型 ⼀个函数可能⽤于处理不同类型的值,这种情况可以使⽤联合类型,例如以下案例中的
message: number | string
function printNumberOrString(message: number | string) {
console.log(message)
}
printNumberOrString('a')
printNumberOrString(1)
- 任意类型 若函数需要处理任意类型的值,则可以使⽤any 类型,例如以下案例中的 message: any
function print(message:any) {
console.log(message)
}
print('a')
print(1)
print(true)
2.2.4.3. 返回值详解
2.2.4.3.1. 特殊类型
若函数没有返回值,则可以使⽤ void 作为返回值类型,其含义为空。
function test(): void {
console.log('hello');
}
2.2.4.3.2. 类型推断
函数的返回值类型可根据函数内容推断出来,因此可以省略不写。
function test() {
console.log('hello');
}
function sum(a: number, b: number) {
return a + b;
}
2.2.4.4. 函数声明特殊语法
- 匿名函数 匿名函数的语法结构简洁,特别适⽤于简单且仅需⼀次性使⽤的场景。
let numbers: number[] = [1, 2, 3, 4, 5]
numbers.forEach(function (number) {
console.log(number);
})
注意:匿名函数能够根据上下⽂推断出参数类型,因此参数类型可以省略。
- 箭头函数 匿名函数的语法还可以进⼀步的简化,只保留参数列表和函数体两个核⼼部分,两者⽤ > 符号连接。
let numbers: number[] = [1, 2, 3, 4, 5]
numbers.forEach((num) => { console.log(num) })
2.2.5. 类(class)
2.2.5.1. 概述
类(class)是⾯向对象编程语⾔中的⼀个重要概念。
⾯向对象编程(Object-Oriented Programming,简称OOP)是⼀种编程范式,其核⼼理念在于 将程序中的数据与操作数据的⽅法有机地组织成对象,从⽽使程序结构更加模块化和易于理解。 通过对象之间的协同合作,实现更为复杂的程序功能。
类(class)是对象的蓝图或模板,它定义了对象的属性(数据)和⾏为(⽅法)。通过类可以创 建多个具有相似结构和⾏为的对象。例如定义⼀个Student/Person类,其对象可以是张三同学、李四同学等等
2.2.5.2. 语法说明
2.2.5.2.1. 类的定义
定义类的语法如下图所示
代码如下:
class Person {
id: number;
name: string;
age: number = 18;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
introduce(): string {
return `hello,I am ${this.name},and I am ${this.age} years old`
}
}
2.2.5.2.2. 对象创建
- 语法
创建对象的关键字为 new ,具体语法如下
let person = new Person(1,'zhangsan')
- 对象属性的访问
console.log(person.name); //读
person.name = 'lisi'; //写
console.log(person.name);
- 对象⽅法的调⽤ 对象创建后,便可通过对象调⽤类中声明的⽅法,如下
let intro = person.introduce();
console.log(intro);
2.2.5.2.3. 静态成员
Typescript 中的类中可以包含静态成员(静态属性和静态⽅法),静态成员⾪属于类本身,⽽不 属于某个对象实例。静态成员通⽤⽤于定义⼀些常量,或者⼯具⽅法。
- 声明静态成员 定义静态成员需要使⽤ static 关键字。
class Constants{
static count:number=1;
}
class Utils{
static toLowerCase(str:string){
return str.toLowerCase();
}
}
console.log(Constants.count);
console.log(Utils.toLowerCase('Hello World'))
- 使⽤静态成员 静态成员⽆需通过对象实例访问,直接通过类本身访问即可。
console.log(Constants.count);
console.log(Utils.toLowerCase('Hello World'));`
2.2.5.3. 继承
继承是⾯向对象编程中的重要机制,允许⼀个类(⼦类或派⽣类)继承另⼀个类(⽗类或基类) 的属性和⽅法。⼦类可以直接使⽤⽗类的特性,并根据需要添加新的特性或覆盖现有的特性。这 种机制赋予⾯向对象程序良好的扩展性。 下⾯通过⼀个例⼦演示继承的特性
class Student extends Person {
classNumber: string;
constructor(id: number, name: string, classNumber: string) {
super(id, name);
this.classNumber = classNumber;
}
introduce(): string {
return super.introduce()+`, and I am a student`;
}
}
let student = new Student(1,'xiaokeai','六年二班');
console.log(student.introduce());
注意:
-
类的继承需要使⽤关键字
-
extends ⼦类构造器中需使⽤
-
super() 调⽤⽗类构造器对继承⾃⽗类的属性进⾏初始化。
-
在⼦类中可以使⽤ this 关键字访问继承⾃⽗类的属性和⽅法。 在⼦类中可以使⽤ super 关键字访问⽗类定义的⽅法。
2.2.5.4. 访问修饰符
访问修饰符(Access Modifiers)⽤于控制类成员(属性、⽅法等)的可访问性。TypeScript提 供了三种访问修饰符,分别是private、protected和 。
class Person {
private id: number;
protected name: string;
public age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
}
class Student extends Person {
}
说明:
-
private 修饰的属性或⽅法是私有的,只能在声明它的类中的被访问。
-
protected 修饰的属性或⽅法是受保护的,只能在声明它的类和其⼦类中被访问。同一个包内的其他类
-
public 修饰的属性或⽅法是公有的,可以在任何地⽅被访问到,默认所有的属性和⽅法都是 public 的
2.2.6. 接⼝(interface)
2.2.6.1. 概述
接⼝(interface)是⾯向对象编程中的另⼀个重要概念。接⼝通常会作为⼀种契约或规范让类 (class)去遵守,确保类实现某些特定的⾏为或功能
2.2.6.2. 语法说明
- 接⼝定义 接⼝使⽤ interface 关键字定义,通常情况下,接⼝中只会包含属性和⽅法的声明,⽽不 包含具体的实现细节,具体的细节由其实现类完成。
interface Person {
id: number;
name: string;
age: number;
introduce(): void;
}
- 接⼝实现 接⼝的实现需要⽤到 及接⼝⽅法的实现逻辑。 implements 关键字,实现类中,需要包含接⼝属性的赋值逻辑,以及接⼝⽅法的实现逻辑。
class Student implements Person {
id: number;
name: string;
age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
introduce(): void {
console.log('Hello,I am a student');
}
}
2.2.6.3. 多态
多态是⾯相对象编程中的⼀个重要概念,它可以使同⼀类型的对象具有不同的⾏为。下⾯我们通 过⼀个具体的案例来体会多态这⼀概念 。 ⾸先,再创建⼀个 Person 接⼝的实现类 Teacher ,如下
class Teacher implements Person {
id: number;
name: string;
age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
introduce(): void {
console.log('Hello,I am a teacher');
}
}
然后分别创建⼀个 Student 对象和⼀个 Teacher 对象,注意两个对象的类型均可以设置 Person ,如下
let p1: Person = new Student(1, 'xiaowang', 22);
let p2: Person = new Teacher(2, 'xiaozhu', 35);
最后分别调⽤p1 和 p2,调⽤同⼀个 introduce() ⽅法,你会发现,同样是 Person 类型的两个对 introduce() ⽅法时,表现出了不同的⾏为,这就是多态。
p1.introduce();//Hello,I am a student
p2.introduce();//Hello,I am a teacher
2.2.6.4. 接⼝的作⽤
在传统的⾯向对象编程的场景中,接⼝主要⽤于设计和组织代码,使代码更加容易扩展和维护。 下⾯举例说明。
假如现在需要实现⼀个订单⽀付系统,按照⾯向对象编程的习惯,⾸先需要定义⼀个订单类 (Order),如下
class Order {
totalAmount: number;
constructor(totalAmount: number) {
this.totalAmount = totalAmount;
}
pay() {
console.log(`AliPay:${this.totalAmount}`);
}
}
很容易预想到,这个系统将来可能需要⽀持其他的⽀付⽅式,为了⽅便代码⽀持新的⽀付⽅式, 我们可以对代码进⾏如下改造。
⾸先定义⼀个⽀付策略的接⼝,接⼝中声明⼀个 pay ⽅法,⽤来规范实现类必须实现⽀付逻 辑
interface PaymentStrategy {
pay(amount: number): void;
}
然后在订单类中增加⼀个 PaymentStrategy 的属性,并且在订单类中的 pay ⽅法中调⽤ PaymentStrategy 的 pay ⽅法,如下
class Order {
totalAmount: number;
paymentStrategy: PaymentStrategy;
constructor(totalAmount: number, paymentStrategy: PaymentStrategy) {
this.totalAmount = totalAmount;
this.paymentStrategy = paymentStrategy;
}
pay() {
this.paymentStrategy.pay(this.totalAmount);
}
}
这样⼀来,之后创建的订单就可以使⽤ AliPay 这个⽀付⽅式了。
//支付案例
class Order {
totalAmount: number;
paymentStrategy?: PaymentStrategy
constructor(totalAmount: number, paymentStrategy?: PaymentStrategy) {
this.totalAmount = totalAmount;
this.paymentStrategy = paymentStrategy
}
pay() {
if (this.paymentStrategy === undefined) {
console.log(`默认Pay:${this.totalAmount}`);
} else {
this.paymentStrategy.pay(this.totalAmount)
}
}
}
//其它支付方法
interface PaymentStrategy {
pay(amount: number): void;
}
class Alipay implements PaymentStrategy {
pay(amount: number) {
console.log(`我是AliPay:${amount}}`);
}
}
class WeiXinpay implements PaymentStrategy {
pay(amount: number) {
console.log(`我是WeiXinPay:${amount}}`);
}
}
let order1 = new Order(2000)
let order2 = new Order(2000, new Alipay())
let order3 = new Order(2000, new WeiXinpay)
order1.pay()
order2.pay()
order3.pay()
2.2.6.5. TS 中的接⼝的特殊性
TypeScript 中的接⼝是⼀个⾮常灵活的概念,除了⽤作类的规范之外,也常⽤于直接描述对象的 类型,例如,现有⼀个变量的定义如下
let person: {name:string, age:number, gender:string} =
{name:'xiaowang', age:20, gender:'男'}
可以看到变量的值为⼀个⼀般对象,变量的类型为 {name:string, age:number, gender: string} ,此时就可以声明⼀个接⼝来描述该对象的类型,如下
interface Person {
name: string;
age: number;
gender: string;
}
let person: Person = {name:'张三', age:10, gender:'男'};
2.2.7. 枚举
2.2.7.1. 概述
枚举(Enumeration)是编程语⾔中常⻅的⼀种数据类型,其主要功能是定义⼀组有限的选项, 例如,⽅向(上、下、左、右)或季节(春、夏、秋、冬)等概念都可以使⽤枚举类型定义。
2.2.7.2. 语法说明
- 枚举定义
枚举的定义需使⽤ enum 关键字,如下
enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER
}
- 枚举使⽤ 枚举的使⽤记住两个原则即可 枚举值的访问 像访问对象属性⼀样访问枚举值,例如 **枚举值的类型 ** 枚举值的类型为 enum 的名称,例如 Season.SPRING 和 Season.SUMMER类型都是 Season
let spring:Season = Season.SPRING;
- 使⽤场景 现需要编写⼀个函数 move ,其功能是根据输⼊的⽅向(上、下、左、右)进⾏移动,此时 就可以先使⽤枚举定义好所有可能的输⼊选项,如下
enum Direction {
UP,
BOTTOM,
LEFT,
RIGHT
}
move 函数的实现如下
function move(direction: Direction) {
if(direction===Direction.UP){
console.log('向上移动');
}else if(direction===Direction.BOTTOM){
console.log('向下移动');
}else if(direction===Direction.LEFT){
console.log('向左移动');
}else{
console.log('向右移动');
}
}
move(Direction.UP);
2.2.7.3. 赋值
在TypeScript 中,枚举实际上是⼀个对象,⽽每个枚举值都是该对象的⼀个属性,并且每个属性 都有具体的值,属性值只⽀持两种类型—— 数字或字符串。
默认情况下,每个属性的值都是数字,并且从 0 开始递增,例如上述案例中的 Direction枚举中, Direction.UP 的值为 0 , Direction.BOTTOM 的值为 1 ,依次类推,具体如下
console.log(Direction.UP) //0
console.log(Direction.BOTTOM) //1
console.log(Direction.LEFT) //2
console.log(Direction.RIGHT) //3
除了使⽤默认的数字作为属性的值,我们还能⼿动为每个属性赋值,例如
enum Direction {
UP = 1,
BOTTOM = 2,
LEFT = 3,
RIGHT = 4
}
console.log(Direction.UP) //1
console.log(Direction.BOTTOM) //2
console.log(Direction.LEFT) //3
console.log(Direction.RIGHT) //4
再例如
enum Direction {
UP = 'up',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right'
}
console.log(Direction.UP) //up
console.log(Direction.BOTTOM) //bottom
console.log(Direction.LEFT) //left
console.log(Direction.RIGHT) //right
通过为枚举属性赋值,可以赋予枚举属性⼀些更有意义的信息,例如以下枚举
enum Color {
Red = 0xFF0000,
Green = 0x00FF00,
Blue = 0x0000FF
}
enum FontSize {
Small = 12,
Medium = 16,
Large = 20,
ExtraLarge = 24
}
2.2.8. 模块化
2.2.8.1. 概述
模块化是指将复杂的程序拆解为多个独⽴的⽂件单元,每个⽂件被称为⼀个模块。
在 TypeScript 中,默认情况下,每个模块都拥有⾃⼰的作⽤域,这意味着在⼀个模块中声明的任何 内容(如变量、函数、类等)在该模块外部是不可⻅的。
为了在⼀个模块中使⽤其他模块的内 容,必须对这些内容进⾏导⼊、导出。
2.2.8.2. 语法说明
- 导出 导出须使⽤ export 关键字,语法如下 moduleOne.ts
export function hello() {
console.log('hello module One');
}
export const str = 'hello world';
const num = 1;
- 导⼊
导⼊须使⽤ import 关键字,语法如下
moduleTwo.ts
import { hello, str } from './moduleOne';
hello();//从1号模块导过来
console.log(str);//导过来的字符
2.2.8.3. 避免命名冲突
若多个模块中具有命名相同的变量、函数等内容,将这些内容导⼊到同⼀模块下就会出现命名冲 突。例如,在上述案例的基础上,⼜增加了⼀个 moduleC,内容如下
export function hello() {
console.log('hello module C');
}
export const str = 'module C';
moduleTwo 同时引⼊ moduleOne 和 moduleC 的内容,如下,显然就会出命名冲突
import { hello, str } from "./moduleOne";
import { hello, str } from "./moduleC";
hello() //?
console.log(str); //?
有多种⽅式可以⽤来解决命名冲突,下⾯逐⼀介绍
- 导⼊重命名 语法如下
import { hello as helloFromOne, str as strFromOne } from "./moduleOne";
import { hello as helloFromC, str as strFromC } from "./moduleC";
helloFromOne();
console.log(strFromOne);
helloFromC();
console.log(strFromC)
- 创建模块对象 上述导⼊重命名的⽅式能够很好的解决命名冲突的问题,但是当冲突内容较多时,这种写法 会⽐较冗⻓。除了导⼊重命名外,还可以将某个模块的内容统⼀导⼊到⼀个模块对象上,这样就能简洁有效的解决命名冲突的问题了,具体语法如下
import * as One from "./moduleOne";
import * as C from "./moduleC";
One.hello();
console.log(One.str);
C.hello();
console.log(C.str);
2.2.8.4. 默认导⼊导出
除了上述导⼊导出的语法之外,还有⼀种语法,叫做默认导⼊导出,这种语法相对简洁⼀些。
- 默认导出
默认导出允许⼀个模块指定⼀个(最多⼀个)默认的导出项,语法如下
moduleOne.ts
export default function hello(){
console.log('moduleOne');
}
- 默认导⼊ 由于每个模块最多有⼀个默认导出,因此默认导⼊⽆需关注导⼊项的原始名称,并且⽆需使 ⽤ {} 。
moduleB.ts
import helloFromOne from "./moduleOne";
由于默认导⼊时⽆需关注导⼊项的名称,所以默认导出⽀持匿名内容,⽐如匿名函数,语法 如下
moduleOne.ts
export default function () {
console.log('moduleB');
}
常见类型转换
let string1:string ="1.7"
let string2:string ="1.7a"
let string3:string ="1.7"
console.log("string1",parseInt(string1))//1
console.log("string2",parseFloat(string2))//1.7
console.log("string3",parseFloat(string3))//1.7
console.log("string3",Number(string3))//1.7
console.log("string3",Number(string2))//Nan
let number1:number=1.1
let number2:number=1.9
console.log("number1",number1.toString())//1.1
console.log("number2",number1.toFixed())//1
console.log("number3",number2.toFixed())//2
console.log("number4",number2.toFixed(2))//1.90
- 作者:黄东华
- 联系方式:18128822181
- 本手册部分内容来源于互联网。如有侵权或疑问,请及时联系作者,我将立即处理并进行删除相关内容。