Featured image of post 速通编程语言|ArkTS

速通编程语言|ArkTS

假设你已经有OPP基础,那么这篇文章可以帮你快速迁移到ArkTs,鸿蒙系统钦定的开发语言

变量

1
2
3
4
5
let hi: string = 'hello'; //指定了类型
hi = 'hello, world'; //重新赋值
let hi2 = 'hello, world';//自动推断

const hello: string = 'hello'; //常量

基本数据类型包括number、string等简单类型,它们可以准确地表示单一的数据类型。直接访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let n1 = 3.14;
let n2 = 3.141592;
let n3 = .5;
let n4 = 1e2;
let bigInt: BigInt = BigInt('999999999999999999999999999999999999999999999999999999999999');

let isDone: boolean = false;

let s1 = 'Hello, world!\n';


let a = 'Success';
let s3 = `The result is ${a}`; //特殊形式,用反向单引号(`)括起来的模板字面量

void类型用于没有返回值的函数:

此类型只有一个值,同样是void。由于void是引用类型,因此它可以用于泛型类型参数。

1
2
3
4
class Class<T> {
  //...
}
let instance: Class <void>

Object类型是所有引用类型的基类型。任何值,包括基本类型的值,都可以直接被赋给Object类型的变量(基本类型值会被自动装箱)。

1
2
3
let o1: Object = 'Alice';
let o2: Object = ['a','b'];
let o3: Object = 1;

array类型,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。

1
let names: string[] = ['Alice', 'Bob', 'Carol'];

枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。

1
2
3
4
enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Red;

enum ColorSet { White = 0xFF, Grey = 0x7F, Black = 0x00 } //显式设置值

Union类型,即联合类型,是由多个类型组合成的引用类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Cat {
  name: string = 'cat';
  // ...
}
class Dog {
  name: string = 'dog';
  // ...
}
class Frog {
  name: string = 'frog';
  // ...
}
type Animal = Cat | Dog | Frog | number | string | null | undefined;
// Cat、Dog、Frog是一些类型(类或接口)

let animal: Animal = new Cat();//Cat 
animal = new Frog();
animal = 42;//number 
animal = 'dog';//string 
animal = undefined;
// 可以将类型为联合类型的变量赋值为任何组成类型的有效值

function foo(animal: Animal) {
  if (animal instanceof Frog) {
    animal.leap();  // animal在这里是Frog类型
  }
  animal.sleep(); // Animal具有sleep方法
}

Aliases类型为匿名类型(如数组、函数、对象字面量或联合类型)提供名称,或为已定义的类型提供替代名称。

1
2
3
4
type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate <T> = (x: T) => boolean;
type NullableObject = Object | null;

运算符

赋值运算符

赋值运算符=,使用方式如x=y。 复合赋值运算符将赋值与运算符组合在一起,例如:a += b 等价于 a = a + b, 其中的 += 即为复合赋值运算符 复合赋值运算符包括:+=、-=、*=、/=、%=、«=、»=、»>=、&=、|=、^=。

比较运算符

值得注意的是下面这一组:

  1. === 如果两个操作数严格相等(对于不同类型的操作数认为是不相等的),则返回true。
  2. !== 如果两个操作数严格不相等(对于不同类型的操作数认为是不相等的),则返回true。
  3. == 如果两个操作数相等,则返回true。
  4. != 如果两个操作数不相等,则返回true。

语句

If

1
2
3
4
5
6
7
if (condition1) {
  // 语句1
} else if (condition2) {
  // 语句2
} else {
  // else语句
}

Switch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
switch (expression) {
  case label1: // 如果label1匹配,则执行
    // 语句1
    break; // 可省略
  case label2:
  case label3: // 如果label2或label3匹配,则执行
    // 语句23
    break; // 可省略
  default:
    // 默认语句
}

三目运算符(条件表达式)

1
2
3
4
5
condition ? expression1 : expression2
For
for ([init]; [condition]; [update]) {
  statements
}

init,update部分可省略

For-of

使用for-of语句可遍历数组、Set、Map、字符串等可迭代的类型。示例如下:

1
2
3
4
5
6
7
for (forVar of IterableExpression) {
  // process forVar
}

for (let ch of 'a string object') {
  console.info(ch);
}

While

1
2
3
while (condition) {
  statements
}

Do-while

1
2
3
do {
  statements
} while (condition)

循环控制

break和continue

异常处理

1
2
3
4
5
6
7
8
try {
  // 可能发生异常的语句块
  throw new Error('this error')
} catch (e) {
  // 异常处理
}finally {
    // 无论是否发生异常都会执行的代码
    }

函数

常规函数

1
2
3
4
function add(x: string, y: string): string { //返回类型可以省略
  let z: string = `${x} ${y}`;
  return z;
}
1
2
3
4
5
6
function hello(name?: string) { //可选参数
}

function multiply(n: number, coeff: number = 2): number { //给出默认值的参数
  return n * coeff;
}

函数的最后一个参数可以是rest参数,格式为…restArgs。rest参数允许函数接收一个由剩余实参组成的数组,类型为任意指定类型,用于处理不定数量的参数输入。

1
2
3
4
5
6
function sum(...numbers: number[]): number {
  let res = 0;
  for (let n of numbers)
    res += n;
  return res;
}

函数类型

函数类型通常用于定义回调函数:

1
2
3
4
5
6
7
type trigFunc = (x: number) => number // 这是一个函数类型

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

do_action(Math.sin); // 将函数作为参数传入

lambda函数

1
2
let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + y

闭包

简单来说,闭包就是一个函数能够“记住”并访问其创建时所在的作用域(Scope)中的变量,即使这个函数在当前作用域之外被调用。 直观理解就是:函数和其相关的词法环境(Lexical Environment)的组合。这个“环境”包含了函数声明时所有可访问的局部变量。

1
2
3
4
5
6
7
8
9
function f(): () => number {
  let count = 0;
  let g = (): number => { count++; return count; };
  return g;
}

let z = f();
z(); // 返回:1
z(); // 返回:2

函数的重载

可以通过编写重载,指定函数的不同调用方式。具体方法是,为同一个函数写入多个同名但签名不同的函数头,函数实现紧随其后。

1
2
3
4
5
6
7
function foo(x: number): void;            /* 第一个函数定义 */
function foo(x: string): void;            /* 第二个函数定义 */
function foo(x: number | string): void {  /* 函数实现 */
}

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

基本结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Person {

  name: string = '';
  surname: string = '';
  
  constructor (n: string, sn: string) { //构造函数
    this.name = n;
    this.surname = sn;
  }
  
  fullName(): string {
    return this.name + ' ' + this.surname;
  }
  
}
let p = new Person('John', 'Smith');
let p1 = new Person(name: 'John', surname: 'Smith')/

为了减少运行时错误并提升执行性能,ArkTS要求所有字段在声明时或构造函数中显式初始化,与标准TS的strictPropertyInitialization模式相同。
如果需要字段可以为空,则这么写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Person {
  name?: string; // 可能为`undefined`

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

  getName(): string | undefined { // 返回类型匹配name的类型
    return this.name;
  }
}

let jack = new Person();
// 假设代码中没有对name赋值,即没有调用"jack.setName('Jack')"
jack.getName()?.length; // 编译成功,没有运行时错误

使用this关键字访问对象内部资源

静态字段

要访问静态字段,需要使用类名

1
2
3
4
5
6
7
8
class Person {
  static numberOfPersons = 0;
  constructor() {
     // ...
  }
}

Person.numberOfPersons;

getter和setter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Person {
  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 p = new Person();
p.age; // 输出0
p.age = -42; // 设置无效age值会抛出错误

静态方法

1
2
3
4
5
6
class Cl {
  static staticMethod(): string {
    return 'this is a static method.';
  }
}
console.info(Cl.staticMethod());

继承

1
2
3
class [extends BaseClassName] [implements listOfInterfaces] {
  // ...
}

关键字super可用于访问父类的实例字段、实例方法和构造函数。

对象字面量

对象字面量是一个表达式,可用于创建类实例并提供一些初始值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class C {
  n: number = 0;
  s: string = '';
}
let c: C = {n: 42, s: 'foo'};

function foo(c: C) {}
let c: C
c = {n: 42, s: 'foo'};  // 使用变量的类型
foo({n: 42, s: 'foo'}); // 使用参数的类型
function bar(): C {
  return {n: 42, s: 'foo'}; // 使用返回类型
}

抽象类和抽象方法

带有abstract修饰符的类称为抽象类

1
2
3
4
5
6
abstract class X {
  field: number;
  constructor(p: number) {
    this.field = p; 
  }
}

带有abstract修饰符的方法称为抽象方法。只有抽象类内才能有抽象方法。

Record类型的对象字面量

泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。

1
2
3
4
5
let map: Record<string, number> = {
  'John': 25,
  'Mary': 21,
}
map['John']; // 25

类型K可以是字符串类型或数值类型(不包括bigint),而V可以是任何类型。

接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 接口:
interface AreaSize {
  calculateAreaSize(): number; // 方法的声明
  someMethod(): void;     // 方法的声明
}
// 实现:
class RectangleSize implements AreaSize {
  private width: number = 0;
  private height: number = 0;
  someMethod(): void {
    console.info('someMethod called');
  }
  calculateAreaSize(): number {
    this.someMethod(); // 调用另一个方法并返回结果
    return this.width * this.height;
  }
}

接口属性可以是字段、getter、setter或getter和setter组合的形式。属性字段只是getter/setter对的便捷写法。以下表达方式是等价的:

1
2
3
interface Style {
  color: string;
}
1
2
3
4
interface Style {
  get color(): string;
  set color(x: string);
}
1
2
3
class StyledRectangle implements Style {
  color: string = '';
}
1
2
3
4
5
class StyledRectangle implements Style {
  private _color: string = '';
  get color(): string { return this._color; }
  set color(x: string) { this._color = x; }
}

泛型

类和接口可以定义为泛型,将参数添加到类型定义中。如以下示例中的类型参数Element:

1
2
3
4
5
6
7
8
class CustomStack<Element> {
  public push(e: Element):void {
    // ...
  }
}

let s = new CustomStack<string>();
s.push('hello');

泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。

1
2
3
4
5
class MyHashMap<Key extends Hashable, Value> {
  public set(k: Key, v: Value) {
    // ...
  }
}

函数也可以使用范型:

1
2
3
function last<T>(x: T[]): T {
  return x[x.length - 1];
}
1
2
3
4
5
6
// 显式设置的类型实参
let res: string = last<string>(['aa', 'bb']);
let res: number = last<number>([1, 2, 3]);
// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
let res: number = last([1, 2, 3]);

范型可以设置默认值:

1
2
3
4
5
6
function foo<T = number>(): void {
  // ...
}
foo();
// 此函数在语义上等价于下面的调用
foo<number>();

空安全

默认情况下,ArkTS中的所有类型都不允许为空。
可以为空值的变量定义为联合类型T | null。

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

非空断言运算符

后缀运算符!可用于断言其操作数为非空。(类似Dart)

1
2
3
4
5
6
class A {
  value: number = 0;
}
function foo(a: A | null) {
  a!.value;  
}

如果运行时a的值非空,可以访问到a的属性;
如果运行时a的值为空,则发生运行时异常

空值合并运算符

空值合并二元运算符??用于检查左侧表达式的求值是否等于null或者undefined。
如果是,则表达式的结果为右侧表达式;
否则,结果为左侧表达式。
换句话说,a ?? b等价于三元运算符(a != null && a != undefined) ? a : b。

可选链

用于在链式调用中阻止调用空对象的方法

1
this.spouse?.nick;

如果spouse为空,则直接对整个表达式返回undefined
如果不为空,则继续(可选链只对它修饰的那个.生效)

模块

导出

可以使用关键字export导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。

1
2
3
4
5
6
export class Point {
}
export let Origin = new Point(0, 0);

export function Distance(p1: Point, p2: Point): number {
}

导入

支持静态绑定和动态绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import * as Utils from './utils';
Utils.X // 表示来自Utils的X
Utils.Y // 表示来自Utils的Y
import { X, Y } from './utils';
X // 表示来自utils的X
Y // 表示来自utils的Y
import { X as Z, Y } from './utils';
Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见

动态导入

1
2
3
4
5
6
// 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;
}
1
2
3
4
5
6
// Index.ts
import("./Calc").then((obj: ESObject) => {
  console.info(obj.add(3, 5));  
}).catch((err: Error) => {
  console.error("Module dynamic import error: ", err);
});

详情见: 动态导入

注解

注解(Annotation)是一种语言特性,它通过添加元数据来改变应用声明的语义。

1
2
3
4
// 注解的声明:
@interface ClassAuthor {
  authorName: string
}
1
2
3
4
5
// 注解的使用:
@ClassAuthor({authorName: "Bob"})
class MyClass {
  // ...
}
本文采用 CC BY 4.0 许可协议,转载请注明出处。
使用 Hugo 构建
主题 StackJimmy 设计