鸿蒙-TypeScript语法

1. 概述

HarmonyOS 应用的主要开发语言是 ArkTS,它由 TypeScript(简称TS)扩展而来,在继承TypeScript语法的基础上进行了一系列优化,使开发者能够以更简洁、更自然的方式开发应用。

注意:TypeScript 本身也是由另一门语言 JavaScript 扩展而来,它主要是在JavaScript的基础上添加了静态类型定义。因此三者的关系如下图所示

2. TypeScript 快速入门

TypeScript提供了一个线上的 Playground 供练习使用,地址为TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript

2.1. 声明

变量声明

常量声明

let用于声明变量,而const用于声明常量,两者的区别是:变量在赋值后可以修改,而常量在赋值后便不能再修改。

let a:number = 100
const b:number = 200;

类型推断

如果一个变量或常量的声明包含了初始值,TS 便可以根据初始值进行类型推断,此时我们就可以不用显式的指定其类型,例如:

let c = 60;
console.log(typeof c); //number

2.2. 常用数据类型

number类型

number表示数字,包括整数和浮点数,例如: 340-2329.5-13.4

let a :number = 340
let b :number = -23
let c :number = 29.5
let d :number = -13.4

string类型

string表示字符串,例如: 你好hello

let a:string = '你好'
let b:string = 'hello'

boolean类型

boolean表示布尔值,可选值为:truefalse

let isOpen:boolean = true
let isDone:boolean = false

数组类型

数组类型定义由两部分组成,元素类型[],例如number[]表示数字数组,string[]表示字符串数组,数组类型的变量可由数组字面量——[item1,item2,item3]进行初始化。

let a: number[] = []
let b: string[] = ['你好', 'hello']

对象类型

对象(object)类型的声明很简单,只需声明属性名称和属性类型即可,例如{id:number,name:string},对象类型的变量可以通过对象字面量——{id:1,name:'zhangsan'}进行初始化。

let person: { id: number, name: string } = { id: 1, name: 'zhangsan' };

2.3. 函数

可选参数:可选参数通过参数名后的?进行标识,例如下面的gender?参数。

function getPersonInfo(name: string, age: number, gender?: string) {
    if (!gender) {
        gender = '未知'
    }
    return `name:${name},age:${age},gender:${gender}`;
}

let p1 = getPersonInfo('zhagnsan', 10, '男')
let p2 = getPersonInfo('lisi', 15);
console.log(p1);
console.log(p2);

默认参数:可在函数的参数列表为参数指定默认值,如以下案例中的gender: string='未知'参数。

function getPersonInfo(name: string, age: number, gender: 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)

特殊类型:若函数没有返回值,则可以使用void作为返回值类型,其含义为空。

function test(): void {
    console.log('hello');
}

类型推断:函数的返回值类型可根据函数内容推断出来,因此可以省略不写。

function test() {
    console.log('hello');
}

function sum(a: number, b: number) {
    console.log(a + b);
}

匿名函数:匿名函数的语法结构简洁,特别适用于简单且仅需一次性使用的场景。

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.4. 类(class)

概述:

类(class)是对象的蓝图或模板,它定义了对象的属性(数据)和行为(方法)。通过类可以创建多个具有相似结构和行为的对象。例如定义一个 Person类,其对象可以有张三李四等等。

通过一个简单案例,学习一下类的定义语法

示例代码:

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.`
    }
}

对象创建:创建对象的关键字为new,具体语法如下

let person = new Person(1,'zhangsan,10);

对象属性的访问

console.log(person.name); //读

person.name = 'lisi'; //写

console.log(person.name);

对象方法的调用:对象创建后,便可通过对象调用类中声明的方法,如下

let intro = person.introduce();
console.log(intro);

静态成员

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'));

继承:

继承是面向对象编程中的重要机制,允许一个类(子类或派生类)继承另一个类(父类或基类)的属性和方法。子类可以直接使用父类的特性,并根据需要添加新的特性或覆盖现有的特性。这种机制赋予面向对象程序良好的扩展性。

下面通过一个例子演示继承的特性

class Student extends Person {
    classNumber: string;
    constructor(id: number, name: string, age: number, classNumber: string) {
        super(id, name, age);
        this.classNumber = classNumber;
    }

    introduce(): string {
        return `hello,I am ${this.name},and I am ${this.age} years old, and I am a student`
    }
}

let student = new Student(1,'xiaoming',10,'三年二班');
console.log(student.introduce());
  • 关键字:extends
  • 新增属性:classNumber:string;
  • 覆盖父类方法:introduce
  • 构造器:子类构造器中需调用super(父类构造器)对继承的父类属性进行初始化。

权限修饰符

权限修饰符用于控制类成员(属性、方法等)的访问权限。它们有助于维护代码的封装性和安全性。TypeScript提供了三种访问修饰符,分别是 publicprivateprotected

  • public :公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的。
  • private:私有的,只能在声明它的类中的被访问。
  • protected:受保护的,只能在声明它的类和其子类中被访问。

2.5. 接口(interface)

概述

接口(interface)是面向对象编程中的另一个重要概念。接口通常会作为类(class)的一种契约或规范,确保类实现了特定的行为或功能。通常情况下,接口中只会包含属性和方法的声明,而不包含具体的实现细节,具体的细节由其实现类完成。

接口定义:接口使用interface关键字定义,具体如下

interface Person {
    id: number;
    name: string;
    age: number;
    job: string;

    introduce(): void;
}

接口实现

接口的实现需要用到implements关键字,实现类中,需要包含接口属性的赋值逻辑,以及接口方法的实现逻辑。

class Teacher implements Person {
    id: number;
    name: string;
    age: number;
    job: string = 'Teacher';

    constructor(id: number, name: string, age: number) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    introduce(): void {
        console.log(`Hello,I am ${this.name}`);
    }
}

作用

在传统的面向对象编程的场景中,接口主要用于设计和组织代码,使代码更加容易扩展和维护。下面举例说明。假如现在需要实现一个订单支付系统,按照面向对象编程的习惯,首先需要定义一个订单类(Order),如下

class Order {
    total_amount: number;

    constructor(total_amount: number) {
        this.total_amount = total_amount;
    }

    pay() {
        console.log(`Pay:${this.total_amount}`);
    }
}

很容易预想到,订单需要将来可能需要支持多种支付方式,为了方便后期让代码支持新的支付方式,我们可以对代码进行如下改造。首先定义一个支付策略的接口,接口中声明了一个pay方法,用来规范实现类必须实现支付逻辑。

interface PaymentStrategy {
    pay(amount: number): void;
}

然后在订单类中增加一个PaymentStrategy的属性,并且在订单类中的pay方法中调用PaymentStrategypay方法,如下

class Order {
    total_amount: number;
    paymentStrategy: PaymentStrategy;

    constructor(total_amount: number, paymentStrategy: PaymentStrategy) {
        this.total_amount = total_amount;
        this.paymentStrategy = paymentStrategy;
    }

    pay() {
        this.paymentStrategy.pay(this.total_amount);
    }
}

这样改造完之后,就可以很容易的在不改变现有代码的情况下,支持新的支付方式了。

比如现在需要支持AliPay,那我们就可以创建AliPay这个类(class)并实现(implement)PaymentStrategy这个接口,如下

class AliPay implements PaymentStrategy {
    pay(amount: number): void {
        console.log(`AliPay:${amount}`);
    }
}

这样一来,之后创建的订单就可以使用AliPay这个支付方式了。

let order = new Order(1000,new AliPay());
order.pay();

TS 中的接口的特殊性

TypeScript 中的接口是一个非常灵活的概念,除了用作类的规范,让类去实现之外,也常用于直接描述对象的类型,例如,现有一个方法的定义如下

function getPersonInfo(person: { id: number, name: string, age: number }) {
    console.log(`[id:${person.id},name:${person.name},age:${person.age}]`);
}

可以看到该函数的参数类型为一个一般对象:{ id: number, name: string, age: number },此时就可以声明一个接口来描述参数类型,如下,这样一来,函数定义就会更加简洁明了。

interface Person {
    id: number;
    name: string;
    age: number;
}

function getPersonInfo(person: Person) {
    console.log(`[id:${person.id},name:${person.name},age:${person.age}]`);
}

为使接口使用起来更加灵活,TypeScript提出了可选属性的概念,语法如下

interface Person {
    id: number;
    name: string;
    age?: number;
}

上述接口中的age字段就是一个可选属性,在声明该接口类型的对象时,便可根据实际情况选择性的包含或者不包含 age字段,如下

getPersonInfo({ id: 1, name: 'zhangsan', age: 10 })
getPersonInfo({ id: 2, name: 'lisi' })

2.6. 枚举

概述

枚举(Enumeration)是一种编程语言中常见的数据类型,用于定义一组有限的命名常量,常用于表示特定的状态、类型或选项,例如方向(上、下、左、右)、季节(春、夏、秋、冬)等。

enum Direction {
    UP,
    BOTTOM,
    LEFT,
    RIGHT
}

function move(direction: Direction) {
    switch (direction) {
        case Direction.UP:
            console.log('向上移动');
            break;
        case Direction.BOTTOM:
            console.log('向下移动');
            break;
        case Direction.LEFT:
            console.log('向左移动');
            break;
        case Direction.RIGHT:
            console.log('向右移动');
            break;
        default:
            console.log('原地不动')
            break;
    }
}

move(Direction.UP);

枚举的使用记住两个原则

  1. 枚举值的访问

像访问对象属性一样访问枚举值,例如Direction.UP

  1. 枚举值的类型

枚举值的类型为enum的名称,例如Direction.UPDirection. BOTTOM等值的类型都是Direction

枚举原理

默认情况下,每个属性的值都是数字,并且从 0 开始递增,例如上述案例中的Direction枚举中,Direction.UP的值为0Direction.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

注意:

多数情况下,我们只是用枚举来表示几种不同的状态,以便在不同的状态下采取不同的行动,这时一般无需关注每个属性具体的值。

2.7. 模块化

概述

模块化是将复杂的程序拆解为多个独立的文件单元,每个文件被称为一个模块。在 TypeScript 中,默认情况下,每个模块都拥有自己的作用域,这意味着在一个模块中声明的任何内容(如变量、函数、类等)在该模块外部是不可见的,除非明确导出。同时,为了在一个模块中使用其他模块的内容,必须先将这些内容显式导入到当前模块中。

导出:导出须使用export关键字,语法如下

export function hello() {
    console.log('hello module A');
}

export const str = 'hello world';

const num = 1;

导入:导入须使用import关键字,语法如下

import { hello, str } from './moduleA';

hello();
console.log(str);

避免命名冲突

若多个模块中具有命名相同的变量、函数等内容,将这些内容导入到同一模块下就会出现命名冲突。例如,在上述案例的基础上,又增加了一个 moduleC,内容如下

export function hello() {
    console.log('hello module C');
}

export const str = 'module C';

moduleB 同时引入 moduleAmoduleC 的内容,如下,显然就会出命名冲突

import { hello, str } from "./moduleA";
import { hello, str } from "./moduleC";

hello() //?
console.log(str); //?

有多种方式可以用来解决命名冲突,下面逐一介绍

  • 导入同时进行重命名
import { hello as helloFromA, str as strFromA } from "./moduleA";
import { hello as helloFromC, str as strFromC } from "./moduleC";

helloFromA();
console.log(strFromA);

helloFromC();
console.log(strFromC);
  • 创建模块对象

上述导入重命名的方式能够很好的解决命名冲突的问题,但是当冲突内容较多时,这种写法会比较冗长。除了导入重命名外,还可以将某个模块的内容统一导入到一个模块对象上,这样就能简洁有效的解决命名冲突的问题了,具体语法如下

import * as A from "./moduleA";
import * as C from "./moduleC";

A.hello();
console.log(A.str);

C.hello();
console.log(C.str);

除了上述导入导出的语法之外,还有一种语法,叫做默认导入导出,这种语法相对简洁一些。

  • 默认导出:默认导出允许一个模块指定一个(最多一个)默认的导出项,语法如下
export default function hello(){
    console.log('moduleA');
}

默认导出支持匿名导出项,语法如下

export default function () {
    console.log('moduleB');
}
  • 默认导入

由于每个模块最多有一个默认导出,因此默认导入无需指定导入项的原名称,并且无需使用{}

import helloFromA from "./moduleA";
import helloFromB from "./moduleB";

上述语法相当于以下语法的简写

import { default as helloFromA } from "./moduleA";
import { default as helloFromB } from "./moduleB";

2.8. 装饰器

概述

在TypeScript中,装饰器是一种特殊类型的声明,可以被附加到类,属性,方法上。装饰器的核心思想是在尽量不改变原始类的定义的情况下,为类添加新的特性。

装饰器使用@Expression的形式。其中,Expression为一个函数,这个函数负责定义为类添加的新特性,其会在运行时被调用。

ArkTS 提供了多种装饰器,同时在鸿蒙应用的开发中我们也会大量使用这些装饰器,因此学习装饰器语法,对于理解鸿蒙应用的执行原理有很大帮助。

装饰器的分类

按照声明位置的不同,装饰器可以分为类装饰器、方法装饰器和属性装饰器等,不同装饰器可用于实现不同的功能。

  • 类装饰器:

类装饰器可以拦截并修改类的构造函数,这使得我们可以在实例化对象时增加一些额外的逻辑。

@ClassDecorator
class A {

}

/**
 * 装饰器函数
 * 在第一次引用A类时执行
 * @param target  被装饰的类
 */
function ClassDecorator (target) {
  // 给目标类添加静态属性
  target.xxx = 'abc'
}

console.log((A as any).xxx)  // abc

  • 方法装饰器

使用方法装饰器可以拦截并修改所装饰的方法,这使得我们可以在调用该方法之前或之后执行一些额外的逻辑。

class A {
  @MethodDecorator
  hello(){
    console.log('hello()');
  }
}

/**
 * 方法装饰器
 * 在第一次调用方法前执行
 * @param target 方法所属类的原型对象
 * @param name 方法名
 * @param descriptor 方法属性对应的描述符对象
 */
function MethodDecorator (target: object, name: string, descriptor: PropertyDescriptor) {
  console.log(`${name}方法将要第一次调用了`, target, name, descriptor);
}

const a = new A()
a.hello()

  • 属性装饰器

属性装饰器可以拦截对属性的读写操作,并在这些操作之前或之后执行一些额外的逻辑。

class A {
  @PropertyDecorator
  name: string;
}

/**
 * 属性装饰器
 * 在第一次创建对象内部初始化属性时执行
 * @param target 属性所属类的原型对象
 * @param name 属性名
 */
function PropertyDecorator (target: object, name: string) {
  console.log(`将要第一次操作${name}属性`, target, name);
}

const a = new A() // 内部会将name属性添加给a对象,自动调用PropertyDecorator

简单案例

下面通过一个简单的案例,演示装饰器的作用,例如现在需要监听某一个属性的变化,当其发生变化时,自动执行一些额外的逻辑。此时就可以通过一个属性装饰器来监视读写操作。

class Person {
  @log
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

function log(target: object, name: string) {
  console.log('----------log')
  let value: any;
  Object.defineProperty(target, name, {
    set (newValue: any) {
      console.log(`监视到${name}属性修改为${newValue}`);
      value = newValue;
    },
    get () {
      console.log(`监视到读取${name}属性`)
      return value;
    }
  })
}

let person = new Person('张三');
person.name='李四'
console.log(person.name)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值