鸿蒙开发笔记-2-ArkTS语言介绍

29 阅读17分钟

ArkTS语言介绍

声明

  • ArkTS通过声明引入变量、常量、函数和类型。
变量声明
  • 以关键字let开头的声明引入变量,该变量在程序执行期间可以具有不同的值。
let hi: string = 'hello';
hi = 'hello, world';
常量声明
  • 以关键字const开头的声明引入只读常量,该常量只能被赋值一次。
const hello: string = 'hello';
// hello="world"//修改时报错
自动类型推断
  • 由于ArkTS是一种静态类型语言,所有数据的类型都必须在编译时确定。 但是,如果一个变量或常量的声明包含了初始值,那么开发者就不需要显式指定其类型。ArkTS规范中列举了所有允许自动推断类型的场景。
let hi1: string = 'hello';
let hi2 = 'hello, world';

类型

Number类型
  • ArkTS提供number和Number类型,任何整数和浮点数都可以被赋给此类型的变量。
let n1 = 3.14;
let n2 = 3.141592;
let n3 = .5;
let n4 = 1e10;
let factorial: (n: number) => number = (n: number): number => {
  if (n <= 1) {
    return 1;
  }
  return n * factorial(n - 1);
};
Boolean类型
  • 由true和false两个逻辑值组成
let isDone: boolean = false;
if (isDone) {
  console.log('Done!');
}
String类型
  • 字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。
let s1 = 'Hello, world!\n';
let s2 = 'this is a string';
let a = 'Success';
  • 字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。
let s3 = `The result is ${a}`;
Void类型
  • void类型用于指定函数没有返回值。
  • 此类型只有一个值,同样是void。由于void是引用类型,因此它可以用于泛型类型参数。
class Class<T> {
  //...
}
let instance: Class <void>
Object类型
  • 是所有引用类型的基类型。任何值,包括基本类型的值(它们会被自动装箱),都可以直接被赋给Object类型的变量。
Array类型
  • 数组可由数组复合字面量(即用方括号括起来的零个或多个表达式的列表,其中每个表达式为数组中的一个元素)来赋值。
let names: string[] = ['Alice', 'Bob', 'Carol'];
Enum类型
  • 又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量
  • 使用枚举常量时必须以枚举类型名称为前缀。
enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Red;
  • 常量表达式可以用于显式设置枚举常量的值
enum ColorSet2 { White = 0xFF, Grey = 0x7F, Black = 0x00 }
let c2: ColorSet2 = ColorSet2.Black;
Union类型
  • 即联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型
class Cat {
  // ...
}

class Dog {
  // ...
}

class Frog {
  // ...
}

type Animal = Cat | Dog | Frog | number
// Cat、Dog、Frog是一些类型(类或接口)
let animal: Animal = new Cat();
animal = new Frog();
animal = 42;
// 可以将类型为联合类型的变量赋值为任何组成类型的有效值
  • 可以用不同的机制获取联合类型中特定类型的值。
class Cat { sleep() {
}; meow() {
} }

class Dog { sleep() {
}; bark() {
} }

class Frog { sleep() {
}; leap() {
} }

type Animal = Cat | Dog | Frog | number

let animal: Animal = new Frog();
if (animal instanceof Frog) {
  let frog: Frog = animal as Frog; // animal在这里是Frog类型
  animal.leap();
  frog.leap();
  // 结果:青蛙跳了两次
}

animal.sleep(); // 任何动物都可以睡觉
Aliases类型
  • 为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称
type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate<T> = (x: T) => Boolean;
type NullableObject = Object | null;

语句

If语句
  • 条件表达式可以是任何类型。但是对于boolean以外的类型,会进行隐式类型转换:
let s4 = 'Hello';
if (s4) {
  console.log(s4); // 打印“Hello”
}
let s5 = 'World';
if (s5.length != 0) {
  console.log(s5); // 打印“World”
}
Switch语句
switch (s5) {
  case "a": // 如果a匹配,则执行
  // 语句1
    break; // 可省略
  case "b":
  case "c": // 如果b或c匹配,则执行
  // 语句23
    break; // 可省略
  default:
// 默认语句
}
条件表达式
  • 由第一个表达式的布尔值来决定返回其它两个表达式中的哪一个
//condition ? expression1 : expression2
let isValid = Math.random() > 0.5 ? true : false;
let message = isValid ? 'Valid' : 'Failed';
For语句
  • 会被重复执行,直到循环退出语句值为false
let sum = 0;
for (let i = 0; i < 10; i += 2) {
  sum += i;
}

//For-of语句
for (let ch of 'a string object') {
  /* process ch */
}
While语句:
let n = 0;
let x = 0;
while (n < 3) {
  n++;
  x += n;
}
Do-while语句:
let i = 0;
do {
  i += 1;
} while (i < 10)
Break语句
  • 可以终止循环语句或switch
let x2 = 0;
while (true) {
  x2++;
  if (x2 > 5) {
    break;
  }
}
//如果break语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
let x3 = 1
label: while (true) {
  switch (x3) {
    case 1:
    // statements
      break label; // 中断while语句
  }
}
Continue语句
  • 会停止当前循环迭代的执行,并将控制传递给下一个迭代
let sum2 = 0;
for (let x = 0; x < 100; x++) {
  if (x % 2 == 0) {
    continue
  }
  sum2 += x;
}
Throw和Try语句
class ZeroDivisor extends Error {}

function divide(a: number, b: number): number {
  if (b == 0) throw new ZeroDivisor();
  return a / b;
}

function process(a: number, b: number) {
  try {
    let res = divide(a, b);
    console.log('result: ' + res);
  } catch (x) {
    console.log('some error');
  }
}

//支持finally语句:
function processData(s: string) {
  let error: Error | null = null;

  try {
    console.log('Data processed: ' + s);
    // ...
    // 可能发生异常的语句
    // ...
  } catch (e) {
    error = e as Error;
    // ...
    // 异常处理
    // ...
  } finally {
    if (error != null) {
      console.log(`Error caught: input='${s}', message='${error.message}'`);
    }
  }
}

函数

函数声明
  • 函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。
function add(x: string, y: string): string {
  let z: string = `${x} ${y}`;
  return z;
}
可选参数
  • 可选参数的格式可为name?: Type
function hello(name?: string) {
  if (name == undefined) {
    console.log('Hello!');
  } else {
    console.log(`Hello, ${name}!`);
  }
}

//可选参数的另一种形式为设置的参数默认值。如果在函数调用中这个参数被省略了,则会使用此参数的默认值作为实参。
function multiply(n: number, coeff: number = 2): number {
  return n * coeff;
}

multiply(2); // 返回2*2
multiply(2, 3); // 返回2*3
Rest参数
  • 函数的最后一个参数可以是rest参数。使用rest参数时,允许函数或方法接受任意数量的实参。
function sum(...numbers: number[]): number {
  let res = 0;
  for (let n of numbers)
    res += n;
  return res;
}

sum() // 返回0
sum(1, 2, 3) // 返回6
返回类型
  • 从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型
// 显式指定返回类型
function foo(): string {
  return 'foo';
}

// 推断返回类型为string
function goo() {
  return 'goo';
}

//不需要返回值的函数的返回类型可以显式指定为void或省略标注。这类函数不需要返回语句
function hi1() {
  console.log('hi');
}

function hi2(): void {
  console.log('hi');
}
函数的作用域
  • 函数中定义的变量和其他实例仅可以在函数内部访问,不能从外部访问。
  • 如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。
函数调用

-调用函数以执行其函数体,实参值会赋值给函数的形参

function join(x: string, y: string): string {
  let z: string = `${x} ${y}`;
  return z;
}

let x = join('hello', 'world');
console.log(x);
函数类型
  • 函数类型通常用于定义回调
type trigFunc = (x: number) => number // 这是一个函数类型

function do_action(f: trigFunc) {
  f(3.141592653589); // 调用函数
}

do_action(Math.sin); // 将函数作为参数传入
箭头函数或Lambda函数
let sum3 = (x: number, y: number): number => {
  return x + y;
}
//箭头函数的返回类型可以省略;省略时,返回类型通过函数体推断。
// 表达式可以指定为箭头函数,使表达更简短,因此以下两种表达方式是等价的:
let sum1 = (x: number, y: number) => {
  return x + y;
}
let sum2 = (x: number, y: number) => x + y
闭包
  • 由函数及声明该函数的环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
  • 在下例中,z是执行f时创建的g箭头函数实例的引用。g的实例维持了对它的环境的引用(变量count存在其中)。因此,当z被调用时,变量count仍可用。
function f(): () => number {
  let count = 0;
  let g = (): number => {
    count++;
    return count;
  };
  return g;
}

let z = f();
z(); // 返回:1
z(); // 返回:2
函数重载
  • 可以通过编写重载,指定函数的不同调用方式。具体方法为,为同一个函数写入多个同名但签名不同的函数头,函数实现紧随其后。
function foo2(x: number): void; /* 第一个函数定义 */

function foo2(x: string): void; /* 第二个函数定义 */

function foo2(x: number | string): void { /* 函数实现 */
}

foo2(123); // OK,使用第一个定义
foo2('aa'); // OK,使用第二个定义

  • 类声明引入一个新类型,并定义其字段、方法和构造函数。
class Person {
  name: string = ''
  surname: string = ''
  constructor(n: string, sn: string) {
    this.name = n;
    this.surname = sn;
  }
  fullName(): string {
    return this.name + ' ' + this.surname;
  }
}

//定义类后,可以使用关键字new创建实例:
let p = new Person('John', 'Smith');
console.log(p.fullName());

//或者,可以使用对象字面量创建实例:
class Point {
  x: number = 0
  y: number = 0
}
let p2: Point = { x: 42, y: 42 };
字段
  • 字段是直接在类中声明的某种类型的变量。
  • 类可以具有实例字段或者静态字段。
实例字段
  • 实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。
  • 要访问实例字段,需要使用类的实例。
class Person2 {
  name: string = ''
  age: number = 0
  constructor(n: string, a: number) {
    this.name = n;
    this.age = a;
  }

  getName(): string {
    return this.name;
  }
}
let p1 = new Person2('Alice', 25);
p1.name;
let p11 = new Person2('Bob', 28);
p11.getName();
静态字段
  • 使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。
  • 要访问静态字段,需要使用类名:
class Person3 {
  static numberOfPersons = 0
  constructor() {
    // ...
    Person3.numberOfPersons++;
    // ...
  }
}
Person3.numberOfPersons;
字段初始化
  • 为了减少运行时的错误和获得更好的执行性能, ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization模式一样。
  • 以下代码是在ArkTS中不合法的代码。
class Person4 {
  name: string // undefined

  setName(n:string): void {
    this.name = n;
  }

  getName(): string {
    // 开发者使用"string"作为返回类型,这隐藏了name可能为"undefined"的事实。
    // 更合适的做法是将返回类型标注为"string | undefined",以告诉开发者这个API所有可能的返回值。
    return this.name;
  }
}

let jack = new Person4();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack.getName().length; // 运行时异常:name is undefined
  • 在ArkTS中,应该这样写代码。
class Person4 {
  name: string = ''

  setName(n:string): void {
    this.name = n;
  }

  // 类型为'string',不可能为"null"或者"undefined"
  getName(): string {
    return this.name;
  }
}
let jack4 = new Person4();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack4.getName().length; // 0, 没有运行时异常
  • 接下来的代码展示了如果name的值可以是undefined,那么应该如何写代码。
class Person5 {
  name?: string // 可能为`undefined`

  setName(n:string): void {
    this.name = n;
  }

  // 编译时错误:name可以是"undefined",所以将这个API的返回值类型标记为string
  // getNameWrong(): string {
  // return this.name;
  // }

  getName(): string | undefined { // 返回类型匹配name的类型
    return this.name;
  }
}
let jack5 = new Person5();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
// 编译时错误:编译器认为下一行代码有可能会访问undefined的属性,报错
// jack5.getName().length; // 编译失败
jack5.getName()?.length; // 编译成功,没有运行时错误

getter和setter
  • 可用于提供对对象属性的受控访问。
  • 在以下示例中,setter用于禁止将age属性设置为无效值:
class Person6 {
  name: string = ''
  private _age: number = 0
  get age(): number {
    return this._age;
  }
  set age(x: number) {
    if (x < 0) {
      throw Error('Invalid age argument');
    }
    this._age = x;
  }
}
let p6 = new Person6();
p6.age; // 输出0
p6.age = -42; // 设置无效age值会抛出错误

方法

  • 方法属于类。类可以定义实例方法或者静态方法。
  • 静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。
  • 实例方法:必须通过类的实例调用实例方法:
class RectangleSize {
  private height: number = 0
  private width: number = 0
  constructor(height: number, width: number) {
    // ...
  }
  calculateArea(): number {
    return this.height * this.width;
  }
}
let square = new RectangleSize(10, 10);
square.calculateArea(); // 输出:100
  • 静态方法:使用关键字static将方法声明为静态,必须通过类名调用静态方法:
class Cl {
  static staticMethod(): string {
    return 'this is a static method.';
  }
}
console.log(Cl.staticMethod());
继承
  • 一个类可以继承另一个类(称为基类),并使用以下语法实现多个接口:
class [extends BaseClassName] [implements listOfInterfaces] {
  // ...
}
  • 继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。
class Person7 {
  name: string = ''
  private _age = 0
  get age(): number {
    return this._age;
  }
}

class Employee extends Person7 {
  salary: number = 0
  calculateTaxes(): number {
    return this.salary * 0.42;
  }
}
//包含implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。
interface DateInterface {
now(): string;
}

class MyDate implements DateInterface {
  now(): string {
    // 在此实现
    return 'now is now';
  }
}

父类访问
  • 关键字super可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口:
class RectangleSize2 {
  protected height: number = 0
  protected width: number = 0

  constructor(h: number, w: number) {
    this.height = h;
    this.width = w;
  }

  draw() {
    /* 绘制边界 */
  }
}

class FilledRectangle extends RectangleSize2 {
  color = ''
  constructor(h: number, w: number, c: string) {
    super(h, w); // 父类构造函数的调用
    this.color = c;
  }

  draw() {
    super.draw(); // 父类方法的调用
    // super.height -可在此处使用
    /* 填充矩形 */
  }
}
方法重写
  • 子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
class RectangleSize3 {
  // ...
  area(): number {
    // 实现
    return 0;
  }
}

class Square extends RectangleSize3 {
  private side: number = 0
  area(): number {
    return this.side * this.side;
  }
}
方法重载签名
  • 通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。
class C {
  foo(x: number): void; /* 第一个签名 */
  foo(x: string): void; /* 第二个签名 */
  foo(x: number | string): void { /* 实现签名 */
  }
}
let c = new C();
c.foo(123); // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名

构造函数

  • 类声明可以包含用于初始化对象状态的构造函数。
  • 如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数, 在这种情况下,默认构造函数使用字段类型的默认值来初始化实例中的字段。
派生类的构造函数
  • 构造函数函数体的第一条语句可以使用关键字super来显式调用直接父类的构造函数。
class RectangleSize4 {
  constructor(width: number, height: number) {
    // ...
  }
}

class Square4 extends RectangleSize4 {
  constructor(side: number) {
    super(side, side);
  }
}
构造函数重载签名
  • 可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的构造函数头,构造函数实现紧随其后。
class C2 {
  constructor(x: number) /* 第一个签名 */
  constructor(x: string) /* 第二个签名 */
  constructor(x: number | string) { /* 实现签名 */
  }
}
let c1 = new C2(123); // OK,使用第一个签名
let c2 = new C2('abc'); // OK,使用第二个签名

可见性修饰符

  • 包括:private、protected和public。默认可见性为public。
//protected修饰符的作用与private修饰符非常相似,不同点是protected修饰的成员允许在派生类中访问|
class Base {
  protected x: string = ''
  private y: string = ''
}

class Derived extends Base {
  foo() {
    this.x = 'a'; // OK,访问受保护成员
    // this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的
  }
}
对象字面量
  • 对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。
//对象字面量的表示方式是:封闭在花括号对({})中的'属性名:值'的列表。
class C3 {
  n: number = 0
  s: string = ''
}
let c3: C3 = { n: 42, s: 'foo' };

//ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:
class C4 {
  n: number = 0
  s: string = ''
}

function foo4(c: C4) {
}

let c4: C4
c4 = { n: 42, s: 'foo' }; // 使用变量的类型
foo4({ n: 42, s: 'foo' }); // 使用参数的类型

function bar(): C4 {
  return { n: 42, s: 'foo' }; // 使用返回类型
}

//也可以在数组元素类型或类字段类型中使用
class C5 {
  n: number = 0
  s: string = ''
}
let cc: C5[] = [{ n: 1, s: 'a' }, { n: 2, s: 'b' }];

Record类型的对象字面量
  • 泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。
// 常用对象字面量来初始化该类型的值:
let map: Record<string, number> = {
  'John': 25,
  'Mary': 21,
}
map['John']; // 25
//类型K可以是字符串类型或数值类型,而V可以是任何类型。
interface PersonInfo {
age: number
salary: number
}
let map2: Record<string, PersonInfo> = {
  'John': { age: 25, salary: 10 },
  'Mary': { age: 21, salary: 20 }
}

接口

-接口声明引入新类型。接口是定义代码协定的常见方式。

// 接口:
interface AreaSize {
color: string // 属性
calculateAreaSize(): number // 方法的声明
someMethod(): void; // 方法的声明
}

// 实现:
class RectangleSize5 implements AreaSize {
  //属性实现
  // color: string = ''
  private _color: string = ''
  get color(): string {
    return this._color;
  }
  set color(x: string) {
    this._color = x;
  }

  private width: number = 0
  private height: number = 0
  //方法实现
  someMethod(): void {
    console.log('someMethod called');
  }
  calculateAreaSize(): number {
    this.someMethod(); // 调用另一个方法并返回结果
    return this.width * this.height;
  }
}
接口继承
  • 接口可以继承其他接口
interface Style {
color: string
}
interface ExtendedStyle extends Style {
width: number
}
泛型类型和函数
  • 泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
  • 泛型类和接口:类和接口可以定义为泛型,将参数添加到类型定义中
class CustomStack<Element> {
  public push(e: Element):void {
    // ...
  }
}
let s = new CustomStack<string>();
s.push('hello');
//编译器在使用泛型类型和函数时会确保类型安全
// 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();
    // ...其他代码...
  }
}
泛型函数
  • 使用泛型函数可编写更通用的代码
function last(x: number[]): number {
  return x[x.length - 1];
}

last([1, 2, 3]); // 3

//如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
function last2<T>(x: T[]): T {
  return x[x.length - 1];
}

// 显式设置的类型实参
last2<string>(['aa', 'bb']);
last2<number>([1, 2, 3]);
// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
last2([1, false, 3.66, "4", new Object()]);

泛型默认值
  • 泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称
class SomeType {}
interface Interface <T1 = SomeType> { }

class Base2 <T2 = SomeType> { }

class Derived1 extends Base2 implements Interface { }

// Derived1在语义上等价于Derived2
class Derived2 extends Base2<SomeType> implements Interface<SomeType> { }

function foo5<T = number>(num:T): void {
  // ...
}

foo5(123);
// 此函数在语义上等价于下面的调用
foo5<number>(123);
空安全
  • 默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(strictNullChecks),但规则更严格。
  • 在下面的示例中,所有行都会导致编译时错误:
// let x: number = null; // 编译时错误
// let y: string = null; // 编译时错误
// let z: number[] = null; // 编译时错误
//可以为空值的变量定义为联合类型T | null。
let x2: number | null = null;
x2 = 1; // ok
x2 = null; // ok
if (x2 != null) { /* do something */
}

//非空断言运算符:后缀运算符!可用于断言其操作数为非空。
//应用于空值时,运算符将抛出错误。否则,值的类型将从T | null更改为T:
class C6 {
  value: number | null = 1;
}

let c6 = new C6();
let y: number;
// y = c6.value + 1; // 编译时错误:无法对可空值作做加法
y = c6.value! + 1; // ok,值为2

//空值合并运算符:a ?? b等价于三元运算符(a != null && a != undefined) ? a : b
class Person8 {
  // ...
  nick: string | null = null
  getNick(): string {
    return this.nick ?? '';
  }
}
可选链
  • 在访问对象属性时,如果该属性是undefined或者null,可选链运算符会返回undefined。
class Person9 {
  nick: string | null = null
  spouse?: Person9

  setSpouse(spouse: Person9): void {
    this.spouse = spouse;
  }

  getSpouseNick(): string | null | undefined {
    return this.spouse?.nick;
  }

  constructor(nick: string) {
    this.nick = nick;
    this.spouse = undefined;
  }
}
let p9: Person9 = new Person9('Alice');
p9.spouse?.nick; // undefined

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

导出

  • 可以使用关键字export导出顶层的声明。
  • 未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
//通过export方式导出,在导入时要加{}。
export class Point2 {
  x: number = 0
  y: number = 0
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

export let Origin = new Point2(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));
}

导入

静态导入
  • 导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:
  1. 导入路径,用于指定导入的模块;
  2. 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。
  • 导入绑定可以有几种形式。
// 1. 假设模块具有路径“./utils”和导出实体“X”和“Y”。
// 2.  导入绑定* as A表示绑定名称“A”,通过A.name可访问从导入路径指定的模块导出的所有实体:
import * as Utils from './utils'

Utils.X // 表示来自Utils的X
Utils.Y // 表示来自Utils的Y
//3. 导入绑定{ ident1, ..., identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:
import { X, Y } from './utils'

X // 表示来自utils的X
Y // 表示来自utils的Y
//4. 如果标识符列表定义了ident as alias,则实体ident将绑定在名称alias下:
import { X as Z, Y } from './utils'

Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见
动态导入
  • 应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。
  • import()语法通常称为动态导入dynamic import , 是一种类似函数的表达式, 用来动态导入模块。 以这种方式调用, 将返回一个promise。
let modulePath = prompt("Which module to load?");
import(modulePath)
  .then(obj => <module object>)
.catch(err => <loading error, e.g. if no such module>)
  • 如果在异步函数中,可以使用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();
}

导入HarmonyOS SDK的开放能力
  • HarmonyOS SDK提供的开放能力(接口)也需要在导入声明后使用。可直接导入接口模块来使用该模块内的所有接口能力,例如:
//import UIAbility from '@ohos.app.ability.UIAbility';
  • 通过导入Kit方式使用开放能力有三种方式:
//方式一:导入Kit下单个模块的接口能力。例如:
import { UIAbility } from '@kit.AbilityKit';
//方式二:导入Kit下多个模块的接口能力。例如:
import { UIAbility, Ability, Context } from '@kit.AbilityKit';
//方式三:导入Kit包含的所有模块的接口能力。例如:
import * as module from '@kit.AbilityKit'; //其中,“module”为别名,可自定义,然后通过该名称调用模块的接口。

顶层语句

  • 模块可以包含除return语句外的任何模块级语句。
  • 如果模块包含主函数(程序入口),则模块的顶层语句将在此函数函数体之前执行。否则,这些语句将在执行模块的其他功能之前执行。

程序入口

  • 程序(应用)的入口是顶层主函数。主函数应具有空参数列表或只有string[]类型的参数。
function main() {
  console.log('this is the program entry');
}

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章