一 空安全

默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(strictNullChecks),但规则更严格。
在下面的示例中,所有行都会导致编译时错误:

let x: number = null;    // 编译时错误
let y: string = null;    // 编译时错误
let z: number[] = null;  // 编译时错误

可以为空值的变量定义为联合类型T | null。

let x: number | null = null;
x = 1;    // ok
x = null; // ok
if (x != null) { /* do something */ }

1.1 非空断言运算符

后缀运算符!可用于断言其操作数为非空。
应用于可空类型的值时,它的编译时类型变为非空类型。例如,类型将从T | null更改为T:

class A {
  value: number = 0;
}

function foo(a: A | null) {
  a.value;   // 编译时错误:无法访问可空值的属性
  a!.value;  // 编译通过,如果运行时a的值非空,可以访问到a的属性;如果运行时a的值为空,则发生运行时异常
}

1.2 空值合并运算符

空值合并二元运算符??用于检查左侧表达式的求值是否等于null或者undefined。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
换句话说,a ?? b等价于三元运算符(a != null && a != undefined) ? a : b。
在以下示例中,getNick方法如果设置了昵称,则返回昵称;否则,返回空字符串:

class Person {
  // ...
  nick: string | null = null;
  getNick(): string {
    return this.nick ?? '';
  }
}

1.3 可选链

在访问对象属性时,如果该属性是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

二 模块

程序可划分为多组编译单元或模块。每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。

2.1 导出

可以使用关键字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));
}

2.2 导入

2.2.1 静态导入

导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:

  • 导入路径,用于指定导入的模块;
  • 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。
    导入绑定可以有几种形式。
    假设模块具有路径“./utils”和导出实体“X”和“Y”。
    导入绑定* as A表示绑定名称“A”,通过A.name可访问从导入路径指定的模块导出的所有实体:
import * as Utils from './utils';
Utils.X // 表示来自Utils的X
Utils.Y // 表示来自Utils的Y

导入绑定{ ident1, …, identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:

import { X, Y } from './utils';
X // 表示来自utils的X
Y // 表示来自utils的Y

如果标识符列表定义了ident as alias,则实体ident将绑定在名称alias下:

import { X as Z, Y } from './utils';
Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见

2.2.2 动态导入

应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。
import()语法通常称为动态导入(dynamic import),是一种类似函数的表达式,用来动态导入模块。以这种方式调用,将返回一个promise。
如下例所示,import(modulePath)可以加载模块并返回一个promise,该promise resolve为一个包含其所有导出的模块对象。该表达式可以在代码中的任意位置调用。

// Calc.ts
export function add(a:number, b:number):number {
  let c = a + b;
  console.info('Dynamic import, %d + %d = %d', a, b, c);
  return c;
}

// Index.ts
import("./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.ts
export 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();
}

2.2.3 动态加载

动态import支持条件延迟加载,支持部分反射功能,可以提升页面的加载速度;动态import支持加载HSP模块/HAR模块/OHPM包/Native库等,并且HAR模块间只有变量动态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的指向:
调用实例方法的对象
正在构造的对象

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐