10-ArkTS 语法入门(4)
10-ArkTS 语法入门(4)
接口
接口声明引入新类型。接口是定义代码协定的常见方式。
任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
接口通常包含属性和方法的声明
示例:
interface Style { color: string; // 属性}interface AreaSize { calculateAreaSize(): number; // 方法的声明 someMethod(): void; // 方法的声明}
实现接口的类示例:
// 接口:interface AreaSize { calculateAreaSize(): number; // 方法的声明 someMethod(): void; // 方法的声明}
// 实现:class RectangleSize implements AreaSize { private width: number = 0; private height: number = 0; someMethod(): void { console.log('someMethod called'); } calculateAreaSize(): number { this.someMethod(); // 调用另一个方法并返回结果 return this.width * this.height; }}
接口属性
接口属性可以是字段、getter、setter 或 getter 和 setter 组合的形式。
属性字段只是 getter/setter 对的便捷写法。以下表达方式是等价的:
interface Style {
color: string;
}
interface Style {
get color(): string;
set color(x: string);
}
实现接口的类也可以使用以下两种方式:
interface Style {
color: string;
}
class StyledRectangle implements Style {
color: string = "";
}
interface Style {
color: string;
}
class StyledRectangle implements Style {
private _color: string = "";
get color(): string {
return this._color;
}
set color(x: string) {
this._color = x;
}
}
接口继承
接口可以继承其他接口,如下面的示例所示:
interface Style {
color: string;
}
interface ExtendedStyle extends Style {
width: number;
}
继承接口包含被继承接口的所有属性和方法,还可以添加自己的属性和方法。
抽象类和接口
抽象类与接口都无法实例化。抽象类是类的抽象,抽象类用来捕捉子类的通用特性,接口是行为的抽象。在 ArkTS 中抽象类与接口的区别如下:
- 一个类只能继承一个抽象类,而一个类可以实现一个或多个接口;
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
- 抽象类里面可以有方法的实现,但是接口完全都是抽象的,不存在方法的实现;
- 抽象类可以有构造函数,而接口不能有构造函数。
泛型类型和函数
泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
泛型类和接口
类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数 Element:
class CustomStack<Element> { public push(e: Element):void { // ... }}
要使用类型 CustomStack,必须为每个类型参数指定类型实参:
let s = new CustomStack<string>();
s.push("hello");
编译器在使用泛型类型和函数时会确保类型安全。参见以下示例:
let s = new CustomStack<string>();
s.push(55); // 将会产生编译时错误
泛型约束
泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的 Key 类型参数必须具有 hash 方法。
interface Hashable { hash(): number;}class MyHashMap<Key extends Hashable, Value> { public set(k: Key, v: Value) { let h = k.hash(); // ...其他代码... }}
在上面的例子中,Key 类型扩展了 Hashable,Hashable 接口的所有方法都可以为 key 调用。
泛型函数
使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
function last(x: number[]): number {
return x[x.length - 1];
}
last([1, 2, 3]); // 3
如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
function last<T>(x: T[]): T {
return x[x.length - 1];
}
现在,该函数可以与任何数组一起使用。
在函数调用中,类型实参可以显式或隐式设置:
// 显式设置的类型实参last<string>(['aa', 'bb']);last<number>([1, 2, 3]);
// 隐式设置的类型实参// 编译器根据调用参数的类型来确定类型实参last([1, 2, 3]);
泛型默认值
泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。
class SomeType {}interface Interface <T1 = SomeType> { }class Base <T2 = SomeType> { }class Derived1 extends Base implements Interface { }// Derived1在语义上等价于Derived2class Derived2 extends Base<SomeType> implements Interface<SomeType> { }
function foo<T = number>(): T { // ...}foo();// 此函数在语义上等价于下面的调用foo<number>();
空安全
默认情况下,ArkTS 中的所有类型都是不可为空的,因此类型的值不能为空。这类似于 TypeScript 的严格空值检查模式(strictNullChecks),但规则更严格。
在下面的示例中,所有行都会导致编译时错误:
let x: number = null; // 编译时错误let y: string = null; // 编译时错误let z: number[] = null; // 编译时错误
可以为空值的变量定义为联合类型 T | null。
let x: number | null = null;
x = 1; // okx = null; // okif (x != null) { /* do something */ }
非空断言运算符
后缀运算符!可用于断言其操作数为非空。
应用于可空类型的值时,它的编译时类型变为非空类型。例如,类型将从 T | null 更改为 T:
class A { value: number = 0;}
function foo(a: A | null) { a.value; // 编译时错误:无法访问可空值的属性 a!.value; // 编译通过,如果运行时a的值非空,可以访问到a的属性;如果运行时a的值为空,则发生运行时异常}
空值合并运算符
空值合并二元运算符??用于检查左侧表达式的求值是否等于 null 或者 undefined。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
换句话说,a ?? b 等价于三元运算符(a != null && a != undefined) ? a : b。
在以下示例中,getNick 方法如果设置了昵称,则返回昵称;否则,返回空字符串:
class Person { // ... nick: string | null = null; getNick(): string { return this.nick ?? ''; }}
可选链
在访问对象属性时,如果该属性是 undefined 或者 null,可选链运算符会返回 undefined。
class Person {
nick: string | null = null;
spouse?: Person;
setSpouse(spouse: Person): void {
this.spouse = spouse;
}
getSpouseNick(): string | null | undefined {
return this.spouse?.nick;
}
constructor(nick: string) {
this.nick = nick;
this.spouse = undefined;
}
}
说明:getSpouseNick 的返回类型必须为 string | null | undefined,因为该方法可能返回 null 或者 undefined。
可选链可以任意长,可以包含任意数量的?.运算符。
在以下示例中,如果一个 Person 的实例有不为空的 spouse 属性,且 spouse 有不为空的 nick 属性,则输出 spouse.nick。否则,输出 undefined:
class Person {
nick: string | null = null;
spouse?: Person;
constructor(nick: string) {
this.nick = nick;
this.spouse = undefined;
}
}
let p: Person = new Person("Alice");
p.spouse?.nick; // undefined
模块
程序可划分为多组编译单元或模块。
每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。
与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。
导出
可以使用关键字 export 导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
export class Point {
x: number = 0;
y: number = 0;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export let Origin = new Point(0, 0);
export function Distance(p1: Point, p2: Point): number {
return Math.sqrt(
(p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
);
}
导入
静态导入
导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:
- 导入路径,用于指定导入的模块;
- 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。
导入绑定可以有几种形式。
假设模块具有路径“./utils”和导出实体“X”和“Y”。
导入绑定* as A 表示绑定名称“A”,通过 A.name 可访问从导入路径指定的模块导出的所有实体:
import * as Utils from './utils'Utils.X // 表示来自Utils的XUtils.Y // 表示来自Utils的Y
导入绑定{ ident1, ..., identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:
import { X, Y } from './utils'X // 表示来自utils的XY // 表示来自utils的Y
如果标识符列表定义了 ident as alias,则实体 ident 将绑定在名称 alias 下:
import { X as Z, Y } from './utils'Z // 表示来自Utils的XY // 表示来自Utils的YX // 编译时错误:'X'不可见
动态导入
应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。
import()语法通常称为动态导入 dynamic import,是一种类似函数的表达式,用来动态导入模块。以这种方式调用,将返回一个 promise。
如下例所示,import(modulePath)可以加载模块并返回一个 promise,该 promise resolve 为一个包含其所有导出的模块对象。该表达式可以在代码中的任意位置调用。
// Calc.tsexport function add(a:number, b:number):number { let c = a + b; console.info('Dynamic import, %d + %d = %d', a, b, c); return c;}
// Index.tsimport("./Calc").then((obj: ESObject) => { console.info(obj.add(3, 5)); }).catch((err: Error) => { console.error("Module dynamic import error: ", err);});
如果在异步函数中,可以使用 let module = await import(modulePath)。
// say.tsexport function hi() { console.log('Hello');}export function bye() { console.log('Bye');}
那么,可以像下面这样进行动态导入:
async function test() {
let ns = await import("./say");
let hi = ns.hi;
let bye = ns.bye;
hi();
bye();
}
更多的使用动态 import 的业务场景和使用实例见动态 import。
导入 HarmonyOS SDK 的开放能力
HarmonyOS SDK 提供的开放能力(接口)也需要在导入声明后使用。可直接导入接口模块来使用该模块内的所有接口能力,例如:
import UIAbility from "@ohos.app.ability.UIAbility";
从 HarmonyOS NEXT Developer Preview 1 版本开始引入 Kit 概念。SDK 对同一个 Kit 下的接口模块进行了封装,开发者在示例代码中可通过导入 Kit 的方式来使用 Kit 所包含的接口能力。其中,Kit 封装的接口模块可查看 SDK 目录下 Kit 子目录中各 Kit 的定义。
通过导入 Kit 方式使用开放能力有三种方式:
-
方式一:导入 Kit 下单个模块的接口能力。例如:
import { UIAbility } from "@kit.AbilityKit";
-
方式二:导入 Kit 下多个模块的接口能力。例如:
import { UIAbility, Ability, Context } from "@kit.AbilityKit";
-
方式三:导入 Kit 包含的所有模块的接口能力。例如:
import * as module from "@kit.AbilityKit";
其中,“module”为别名,可自定义,然后通过该名称调用模块的接口。
说明
方式三可能会导入过多无需使用的模块,导致编译后的 HAP 包太大,占用过多资源,请谨慎使用。
顶层语句
顶层语句是指在模块的最外层直接编写的语句,这些语句不被包裹在任何函数、类、块级作用域中。顶层语句包括变量声明、函数声明、表达式等。
关键字
this
关键字 this 只能在类的实例方法中使用。
示例
class A {
count: string = "a";
m(i: string): void {
this.count = i;
}
}
使用限制:
- 不支持 this 类型
- 不支持在函数和类的静态方法中使用 this
示例
class A { n: number = 0; f1(arg1: this) {} // 编译时错误,不支持this类型 static f2(arg1: number) { this.n = arg1; // 编译时错误,不支持在类的静态方法中使用this }}
function foo(arg1: number) { this.n = i; // 编译时错误,不支持在函数中使用this}
关键字 this 的指向:
- 调用实例方法的对象
- 正在构造的对象
- 0回答
- 3粉丝
- 0关注
- 07-ArkTS语法入门(1)
- 08-ArkTS 语法入门(2)
- 09-ArkTS 语法入门(3)
- 「Mac玩转仓颉内测版4」入门篇4 - Cangjie变量命名规则
- 14-从TypeScript到ArkTS的适配规则(4)
- ArkTS第三方库的语法与使用详解
- 「Mac玩转仓颉内测版3」入门篇3 - Cangjie的基本语法与结构
- Flutter鸿蒙入门
- ArkTS语言简介
- OpenHarmony鸿蒙入门指南
- 「Mac畅玩鸿蒙与硬件4」鸿蒙开发环境配置篇4 - DevEco Studio高效使用技巧
- 「Mac畅玩鸿蒙与硬件 10」鸿蒙开发环境配置篇10 - 项目实战:计数器应用
- OpenHarmony新手入门推荐
- 鸿蒙Flutter实战:10-常见问题集合
- 17-ArkTs 常见错误