• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# ArkTS语言介绍
2
3ArkTS是一种为构建高性能应用而设计的编程语言。ArkTS在继承TypeScript语法的基础上进行了优化,以提供更高的性能和开发效率。
4
5随着移动设备在人们的日常生活中变得越来越普遍,许多编程语言在设计之初没有考虑到移动设备,导致应用的运行缓慢、低效、功耗大,针对移动环境的编程语言优化需求也越来越大。ArkTS是专为解决这些问题而设计的,聚焦于提高运行效率。
6
7目前流行的编程语言TypeScript是在JavaScript基础上通过添加类型定义扩展而来的,而ArkTS则是TypeScript的进一步扩展。TypeScript深受开发者的喜爱,因为它提供了一种更结构化的JavaScript编码方法。ArkTS旨在保持TypeScript的大部分语法,为现有的TypeScript开发者实现无缝过渡,让移动开发者快速上手ArkTS。
8
9ArkTS的一大特性是它专注于低运行时开销。ArkTS对TypeScript的动态类型特性施加了更严格的限制,以减少运行时开销,提高执行效率。通过取消动态类型特性,ArkTS代码能更有效地被运行前编译和优化,从而实现更快的应用启动和更低的功耗。
10
11与JavaScript的互通性是ArkTS语言设计中的关键考虑因素。鉴于许多移动应用开发者希望重用其TypeScript和JavaScript代码和库,ArkTS提供了与JavaScript的无缝互通,使开发者可以很容易地将JavaScript代码集成到他们的应用中。这意味着开发者可以利用现有的代码和库进行ArkTS开发。
12
13为了确保应用开发的最佳体验,ArkTS提供对方舟开发框架ArkUI的声明式语法和其他特性的支持。由于此部分特性不在既有TypeScript的范围内,因此我们在《ArkUI支持》一章中提供了详细的ArkUI示例。
14
15本教程将指导开发者了解ArkTS的核心功能、语法和最佳实践,使开发者能够使用ArkTS高效构建高性能的移动应用。
16
17如需更详细了解ArkTS语言,可见[ArkTS具体指南](../arkts-utils/arkts-overview.md)<!--RP1--><!--RP1End-->。
18
19## 基本知识
20
21### 声明
22
23ArkTS通过声明引入变量、常量、函数和类型。
24
25#### 变量声明
26
27以关键字`let`开头的声明引入变量,该变量在程序执行期间可以具有不同的值。
28
29```typescript
30let hi: string = 'hello';
31hi = 'hello, world';
32```
33
34#### 常量声明
35
36以关键字`const`开头的声明引入只读常量,该常量只能被赋值一次。
37
38```typescript
39const hello: string = 'hello';
40```
41
42对常量重新赋值会造成编译时错误。
43
44#### 自动类型推断
45
46由于ArkTS是一种静态类型语言,所有数据的类型都必须在编译时确定。
47
48但是,如果一个变量或常量的声明包含了初始值,那么开发者就不需要显式指定其类型。ArkTS规范中列举了所有允许自动推断类型的场景。
49
50以下示例中,两条声明语句都是有效的,两个变量都是`string`类型:
51
52```typescript
53let hi1: string = 'hello';
54let hi2 = 'hello, world';
55```
56
57### 类型
58
59#### 基本类型和引用类型
60
61基本数据类型包括`number`、`string`等简单类型,它们可以准确地表示单一的数据类型。基本类型确保数据在存储和访问时是直接的,比较时直接比较其值。
62
63ArkTS中的引用类型如对象、数组和函数等,是通过引用访问的复杂数据结构。对象和数组可以包含多个值或键值对,函数则可以封装可执行的代码逻辑。引用类型在内存中通过指针访问数据,修改引用会影响原始数据。
64
65#### `number`类型
66
67ArkTS提供`number`类型,任何整数和浮点数都可以被赋给此类型的变量。
68
69数字字面量包括整数字面量和十进制浮点数字面量。
70
71整数字面量包括以下类别:
72
73* 由数字序列组成的十进制整数。例如:`0`、`117`、`-345`
74* 以0x(或0X)开头的十六进制整数,可以包含数字(0-9)和字母a-f或A-F。例如:`0x1123`、`0x00111`、`-0xF1A7`
75* 以0o(或0O)开头的八进制整数,只能包含数字(0-7)。例如:`0o777`
76* 以0b(或0B)开头的二进制整数,只能包含数字0和1。例如:`0b11`、`0b0011`、`-0b11`
77
78浮点字面量包括以下:
79
80* 十进制整数,可为有符号数(即,前缀为“+”或“-”)
81* 小数点(“.”)
82* 小数部分(由十进制数字字符串表示)
83* 以“e”或“E”开头的指数部分,后跟有符号(即,前缀为“+”或“-”)或无符号整数
84
85示例:
86
87```typescript
88let n1 = 3.14;
89let n2 = 3.141592;
90let n3 = .5;
91let n4 = 1e2;
92
93function factorial(n: number): number {
94  if (n <= 1) {
95    return 1;
96  }
97  return n * factorial(n - 1);
98}
99
100factorial(n1)  //  7.660344000000002
101factorial(n2)  //  7.680640444893748
102factorial(n3)  //  1
103factorial(n4)  //  9.33262154439441e+157
104```
105
106`number`类型在表示大整数时会造成精度丢失。在开发时可以按需使用`bigInt`类型来确保精度:
107
108```typescript
109
110let bigIntger: BigInt = BigInt('999999999999999999999999999999999999999999999999999999999999');
111console.log('bigIntger' + bigIntger.toString());
112
113```
114
115#### `boolean`类型
116
117`boolean`类型由`true`和`false`两个逻辑值组成。
118
119通常在条件语句中使用`boolean`类型的变量:
120
121```typescript
122let isDone: boolean = false;
123
124// ...
125
126if (isDone) {
127  console.log ('Done!');
128}
129```
130
131#### `string`类型
132
133`string`代表字符序列;可以使用转义字符来表示字符。
134
135字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。字符串字面量还有一特殊形式,是用反向单引号(\`)括起来的模板字面量。
136
137```typescript
138let s1 = 'Hello, world!\n';
139let s2 = "this is a string";
140let a = 'Success';
141let s3 = `The result is ${a}`;
142```
143
144#### `void`类型
145
146`void`类型用于指定函数没有返回值。
147此类型只有一个值,同样是`void`。由于`void`是引用类型,因此它可以用于泛型类型参数。
148
149```typescript
150class Class<T> {
151  //...
152}
153let instance: Class <void>
154```
155
156#### `Object`类型
157
158`Object`类型是所有引用类型的基类型。任何值,包括基本类型的值(它们会被自动装箱),都可以直接被赋给`Object`类型的变量。`object`类型则用于表示除基本类型外的类型。
159
160#### `array`类型
161
162`array`,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。
163数组可由数组复合字面量(即用方括号括起来的零个或多个表达式的列表,其中每个表达式为数组中的一个元素)来赋值。数组的长度由数组中元素的个数来确定。数组中第一个元素的索引为0。
164
165以下示例将创建包含三个元素的数组:
166
167```typescript
168let names: string[] = ['Alice', 'Bob', 'Carol'];
169```
170
171#### `enum`类型
172
173`enum`类型,又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。
174使用枚举常量时必须以枚举类型名称为前缀。
175
176```typescript
177enum ColorSet { Red, Green, Blue }
178let c: ColorSet = ColorSet.Red;
179```
180
181常量表达式可以用于显式设置枚举常量的值。
182
183```typescript
184enum ColorSet { White = 0xFF, Grey = 0x7F, Black = 0x00 }
185let c: ColorSet = ColorSet.Black;
186```
187
188#### `Union`类型
189
190`union`类型,即联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。
191
192```typescript
193class Cat {
194  name: string = 'cat';
195  // ...
196}
197class Dog {
198  name: string = 'dog';
199  // ...
200}
201class Frog {
202  name: string = 'frog';
203  // ...
204}
205type Animal = Cat | Dog | Frog | number;
206// Cat、Dog、Frog是一些类型(类或接口)
207
208let animal: Animal = new Cat();
209animal = new Frog();
210animal = 42;
211// 可以将类型为联合类型的变量赋值为任何组成类型的有效值
212```
213
214可以用不同的机制获取联合类型中特定类型的值。
215示例:
216
217```typescript
218class Cat { sleep () {}; meow () {} }
219class Dog { sleep () {}; bark () {} }
220class Frog { sleep () {}; leap () {} }
221
222type Animal = Cat | Dog | Frog;
223
224function foo(animal: Animal) {
225  if (animal instanceof Frog) {
226    animal.leap();  // animal在这里是Frog类型
227  }
228  animal.sleep(); // Animal具有sleep方法
229}
230```
231
232#### `Aliases`类型
233
234`Aliases`类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。
235
236```typescript
237type Matrix = number[][];
238type Handler = (s: string, no: number) => string;
239type Predicate <T> = (x: T) => boolean;
240type NullableObject = Object | null;
241```
242
243### 运算符
244
245#### 赋值运算符
246
247赋值运算符`=`,使用方式如`x=y`。
248
249复合赋值运算符将赋值与运算符组合在一起,其中`x op = y`等于`x = x op y`。
250
251复合赋值运算符列举如下:`+=`、`-=`、`*=`、`/=`、`%=`、`<<=`、`>>=`、`>>>=`、`&=`、`|=`、`^=`。
252
253#### 比较运算符
254
255| 运算符| 说明                                                 |
256| -------- | ------------------------------------------------------------ |
257| `===`    | 如果两个操作数严格相等(对于不同类型的操作数认为是不相等的),则返回true。 |
258| `!==`    | 如果两个操作数严格不相等(对于不同类型的操作数认为是不相等的),则返回true。 |
259| `==`     | 如果两个操作数相等,则返回true。 |
260| `!=`     | 如果两个操作数不相等,则返回true。    |
261| `>`      | 如果左操作数大于右操作数,则返回true。 |
262| `>=`     | 如果左操作数大于或等于右操作数,则返回true。 |
263| `<`      | 如果左操作数小于右操作数,则返回true。    |
264| `<=`     | 如果左操作数小于或等于右操作数,则返回true。 |
265
266#### 算术运算符
267
268一元运算符为`-`、`+`、`--`、`++`。
269
270二元运算符列举如下:
271
272| 运算符| 说明             |
273| -------- | ------------------------ |
274| `+`      | 加法                |
275| `-`      | 减法             |
276| `*`      | 乘法          |
277| `/`      | 除法                |
278| `%`      | 除法后余数|
279
280#### 位运算符
281
282| 运算符 | 说明                                                 |
283| --------- | ------------------------------------------------------------ |
284| `a & b`   | 按位与:如果两个操作数的对应位都为1,则将这个位设置为1,否则设置为0。|
285| `a \| b`  | 按位或:如果两个操作数的相应位中至少有一个为1,则将这个位设置为1,否则设置为0。|
286| `a ^ b`   | 按位异或:如果两个操作数的对应位不同,则将这个位设置为1,否则设置为0。|
287| `~ a`     | 按位非:反转操作数的位。               |
288| `a << b`  | 左移:将a的二进制表示向左移b位。|
289| `a >> b`  | 算术右移:将a的二进制表示向右移b位,带符号扩展。|
290| `a >>> b` | 逻辑右移:将a的二进制表示向右移b位,左边补0。|
291
292#### 逻辑运算符
293
294| 运算符  | 说明|
295| ---------- | ----------- |
296| `a && b`   | 逻辑与 |
297| `a \|\| b` | 逻辑或 |
298| `! a`      | 逻辑非 |
299
300### 语句
301
302#### `If`语句
303
304`if`语句用于需要根据逻辑条件执行不同语句的场景。当逻辑条件为真时,执行对应的一组语句,否则执行另一组语句(如果有的话)。
305`else`部分也可能包含`if`语句。
306
307`if`语句如下所示:
308
309```typescript
310if (condition1) {
311  // 语句1
312} else if (condition2) {
313  // 语句2
314} else {
315  // else语句
316}
317```
318
319条件表达式可以是任何类型。但是对于`boolean`以外的类型,会进行隐式类型转换:
320
321```typescript
322let s1 = 'Hello';
323if (s1) {
324  console.log(s1); // 打印“Hello”
325}
326
327let s2 = 'World';
328if (s2.length != 0) {
329  console.log(s2); // 打印“World”
330}
331```
332
333#### `Switch`语句
334
335使用`switch`语句来执行与`switch`表达式值匹配的代码块。
336
337`switch`语句如下所示:
338
339```typescript
340switch (expression) {
341  case label1: // 如果label1匹配,则执行
342    // ...
343    // 语句1
344    // ...
345    break; // 可省略
346  case label2:
347  case label3: // 如果label2或label3匹配,则执行
348    // ...
349    // 语句23
350    // ...
351    break; // 可省略
352  default:
353    // 默认语句
354}
355```
356
357如果`switch`表达式的值等于某个label的值,则执行相应的语句。
358
359如果没有任何一个label值与表达式值相匹配,并且`switch`具有`default`子句,那么程序会执行`default`子句对应的代码块。
360
361`break`语句(可选的)允许跳出`switch`语句并继续执行`switch`语句之后的语句。
362
363如果没有`break`语句,则执行`switch`中的下一个label对应的代码块。
364
365#### 条件表达式
366
367条件表达式由第一个表达式的布尔值来决定返回其它两个表达式中的哪一个。
368
369示例如下:
370
371```typescript
372condition ? expression1 : expression2
373```
374
375如果`condition`的值为真值(转换后为`true`的值),则使用`expression1`作为该表达式的结果;否则,使用`expression2`。
376
377示例:
378
379```typescript
380let message = Math.random() > 0.5 ? 'Valid' : 'Failed';
381```
382
383#### `For`语句
384
385`for`语句会被重复执行,直到循环退出语句值为`false`。
386
387`for`语句如下所示:
388
389```typescript
390for ([init]; [condition]; [update]) {
391  statements
392}
393```
394
395`for`语句的执行流程如下:
396
3971、 执行`init`表达式(如有)。此表达式通常初始化一个或多个循环计数器。
3982、 计算`condition`。如果它为真值(转换后为`true`的值),则执行循环主体的语句。如果它为假值(转换后为`false`的值),则`for`循环终止。
3993、 执行循环主体的语句。
4004、 如果有`update`表达式,则执行该表达式。
4015、 回到步骤2。
402
403示例:
404
405```typescript
406let sum = 0;
407for (let i = 0; i < 10; i += 2) {
408  sum += i;
409}
410```
411
412#### `For-of`语句
413
414使用`for-of`语句可遍历数组或字符串。示例如下:
415
416```typescript
417for (forVar of expression) {
418  statements
419}
420```
421
422示例:
423
424```typescript
425for (let ch of 'a string object') {
426  /* process ch */
427}
428```
429
430#### `While`语句
431
432只要`condition`为真值(转换后为`true`的值),`while`语句就会执行`statements`语句。示例如下:
433
434```typescript
435while (condition) {
436  statements
437}
438```
439
440示例:
441
442```typescript
443let n = 0;
444let x = 0;
445while (n < 3) {
446  n++;
447  x += n;
448}
449```
450
451#### `Do-while`语句
452
453如果`condition`的值为真值(转换后为`true`的值),那么`statements`语句会重复执行。示例如下:
454
455```typescript
456do {
457  statements
458} while (condition)
459```
460
461示例:
462
463```typescript
464let i = 0;
465do {
466  i += 1;
467} while (i < 10)
468```
469
470#### `Break`语句
471
472使用`break`语句可以终止循环语句或`switch`。
473
474示例:
475
476```typescript
477let x = 0;
478while (true) {
479  x++;
480  if (x > 5) {
481    break;
482  }
483}
484```
485
486如果`break`语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
487
488示例:
489
490```typescript
491let x = 1;
492label: while (true) {
493  switch (x) {
494    case 1:
495      // statements
496      break label; // 中断while语句
497  }
498}
499```
500
501#### `Continue`语句
502
503`continue`语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。
504
505示例:
506
507```typescript
508let sum = 0;
509for (let x = 0; x < 100; x++) {
510  if (x % 2 == 0) {
511    continue;
512  }
513  sum += x;
514}
515```
516
517#### `Throw`和`Try`语句
518
519`throw`语句用于抛出异常或错误:
520
521```typescript
522throw new Error('this error')
523```
524
525`try`语句用于捕获和处理异常或错误:
526
527```typescript
528try {
529  // 可能发生异常的语句块
530} catch (e) {
531  // 异常处理
532}
533```
534
535下面的示例中`throw`和`try`语句用于处理除数为0的错误:
536
537```typescript
538class ZeroDivisor extends Error {}
539
540function divide (a: number, b: number): number{
541  if (b == 0) throw new ZeroDivisor();
542  return a / b;
543}
544
545function process (a: number, b: number) {
546  try {
547    let res = divide(a, b);
548    console.log('result: ' + res);
549  } catch (x) {
550    console.log('some error');
551  }
552}
553```
554
555支持`finally`语句:
556
557```typescript
558function processData(s: string) {
559  let error: Error | null = null;
560
561  try {
562    console.log('Data processed: ' + s);
563    // ...
564    // 可能发生异常的语句
565    // ...
566  } catch (e) {
567    error = e as Error;
568    // ...
569    // 异常处理
570    // ...
571  } finally {
572    if (error != null) {
573      console.log(`Error caught: input='${s}', message='${error.message}'`);
574    }
575  }
576}
577```
578
579## 函数
580
581### 函数声明
582
583函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。
584
585以下示例是一个简单的函数,包含两个`string`类型的参数,返回类型为`string`:
586
587```typescript
588function add(x: string, y: string): string {
589  let z: string = `${x} ${y}`;
590  return z;
591}
592```
593
594在函数声明中,必须为每个参数标记类型。如果参数为可选参数,那么允许在调用函数时省略该参数。函数的最后一个参数可以是rest参数。
595
596### 可选参数
597
598可选参数的格式可为`name?: Type`。
599
600```typescript
601function hello(name?: string) {
602  if (name == undefined) {
603    console.log('Hello!');
604  } else {
605    console.log(`Hello, ${name}!`);
606  }
607}
608```
609
610可选参数的另一种形式为设置的参数默认值。如果在函数调用中这个参数被省略了,则会使用此参数的默认值作为实参。
611
612```typescript
613function multiply(n: number, coeff: number = 2): number {
614  return n * coeff;
615}
616multiply(2);  // 返回2*2
617multiply(2, 3); // 返回2*3
618```
619
620### Rest参数
621
622函数的最后一个参数可以是rest参数。rest参数的格式为`...restArgs`。rest参数允许函数接收一个由剩余实参组成的数组,用于处理不定数量的参数输入。
623
624```typescript
625function sum(...numbers: number[]): number {
626  let res = 0;
627  for (let n of numbers)
628    res += n;
629  return res;
630}
631
632sum(); // 返回0
633sum(1, 2, 3); // 返回6
634```
635
636### 返回类型
637
638如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。
639
640```typescript
641// 显式指定返回类型
642function foo(): string { return 'foo'; }
643
644// 推断返回类型为string
645function goo() { return 'goo'; }
646```
647
648不需要返回值的函数的返回类型可以显式指定为`void`或省略标注。这类函数不需要返回语句。
649
650以下示例中两种函数声明方式都是有效的:
651
652```typescript
653function hi1() { console.log('hi'); }
654function hi2(): void { console.log('hi'); }
655```
656
657### 函数的作用域
658
659函数中定义的变量和其他实例仅可以在函数内部访问,不能从外部访问。
660
661如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。
662
663### 函数调用
664
665调用函数以执行其函数体,实参值会赋值给函数的形参。
666
667如果函数定义如下:
668
669```typescript
670function join(x: string, y: string): string {
671  let z: string = `${x} ${y}`;
672  return z;
673}
674```
675
676则此函数的调用需要包含两个`string`类型的参数:
677
678```typescript
679let x = join('hello', 'world');
680console.log(x);
681```
682
683### 函数类型
684
685函数类型通常用于定义回调:
686
687```typescript
688type trigFunc = (x: number) => number // 这是一个函数类型
689
690function do_action(f: trigFunc) {
691  f(3.141592653589); // 调用函数
692}
693
694do_action(Math.sin); // 将函数作为参数传入
695```
696
697### 箭头函数(又名Lambda函数)
698
699函数可以定义为箭头函数,例如:
700
701```typescript
702let sum = (x: number, y: number): number => {
703  return x + y;
704}
705```
706
707箭头函数的返回类型可以省略;省略时,返回类型通过函数体推断。
708
709表达式可以指定为箭头函数,使表达更简短,因此以下两种表达方式是等价的:
710
711```typescript
712let sum1 = (x: number, y: number) => { return x + y; }
713let sum2 = (x: number, y: number) => x + y
714```
715
716### 闭包
717
718闭包是由函数及声明该函数的环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
719
720在下例中,`f`函数返回了一个闭包,它捕获了`count`变量,每次调用`z`,`count`的值会被保留并递增。
721
722```typescript
723function f(): () => number {
724  let count = 0;
725  let g = (): number => { count++; return count; };
726  return g;
727}
728
729let z = f();
730z(); // 返回:1
731z(); // 返回:2
732```
733
734### 函数重载
735
736我们可以通过编写重载,指定函数的不同调用方式。具体方法为,为同一个函数写入多个同名但签名不同的函数头,函数实现紧随其后。
737
738```typescript
739function foo(x: number): void;            /* 第一个函数定义 */
740function foo(x: string): void;            /* 第二个函数定义 */
741function foo(x: number | string): void {  /* 函数实现 */
742}
743
744foo(123);     //  OK,使用第一个定义
745foo('aa'); // OK,使用第二个定义
746```
747
748不允许重载函数有相同的名字以及参数列表,否则将会编译报错。
749
750## 类
751
752类声明引入一个新类型,并定义其字段、方法和构造函数。
753
754在以下示例中,定义了`Person`类,该类具有字段`name`和`surname`、构造函数和方法`fullName`:
755
756```typescript
757class Person {
758  name: string = '';
759  surname: string = '';
760  constructor (n: string, sn: string) {
761    this.name = n;
762    this.surname = sn;
763  }
764  fullName(): string {
765    return this.name + ' ' + this.surname;
766  }
767}
768```
769
770定义类后,可以使用关键字`new`创建实例:
771
772```typescript
773let p = new Person('John', 'Smith');
774console.log(p.fullName());
775```
776
777或者,可以使用对象字面量创建实例:
778
779```typescript
780class Point {
781  x: number = 0;
782  y: number = 0;
783}
784let p: Point = {x: 42, y: 42};
785```
786
787### 字段
788
789字段是直接在类中声明的某种类型的变量。
790
791类可以具有实例字段或者静态字段。
792
793#### 实例字段
794
795实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。
796
797要访问实例字段,需要使用类的实例。
798
799```typescript
800class Person {
801  name: string = '';
802  age: number = 0;
803  constructor(n: string, a: number) {
804    this.name = n;
805    this.age = a;
806  }
807
808  getName(): string {
809    return this.name;
810  }
811}
812
813let p1 = new Person('Alice', 25);
814p1.name;
815let p2 = new Person('Bob', 28);
816p2.getName();
817```
818
819#### 静态字段
820
821使用关键字`static`将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。
822
823要访问静态字段,需要使用类名:
824
825```typescript
826class Person {
827  static numberOfPersons = 0;
828  constructor() {
829     // ...
830     Person.numberOfPersons++;
831     // ...
832  }
833}
834
835Person.numberOfPersons;
836```
837
838#### 字段初始化
839
840为了减少运行时的错误和获得更好的执行性能,
841ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的`strictPropertyInitialization`模式一样。
842
843以下代码是在ArkTS中不合法的代码。
844
845```typescript
846class Person {
847  name: string; // undefined
848
849  setName(n:string): void {
850    this.name = n;
851  }
852
853  getName(): string {
854    // 开发者使用"string"作为返回类型,这隐藏了name可能为"undefined"的事实。
855    // 更合适的做法是将返回类型标注为"string | undefined",以告诉开发者这个API所有可能的返回值。
856    return this.name;
857  }
858}
859
860let jack = new Person();
861// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
862jack.getName().length; // 运行时异常:name is undefined
863```
864
865在ArkTS中,应该这样写代码。
866
867```typescript
868class Person {
869  name: string = '';
870
871  setName(n:string): void {
872    this.name = n;
873  }
874
875  // 类型为'string',不可能为"null"或者"undefined"
876  getName(): string {
877    return this.name;
878  }
879}
880
881
882let jack = new Person();
883// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
884jack.getName().length; // 0, 没有运行时异常
885```
886
887接下来的代码展示了如果`name`的值可以是`undefined`,那么应该如何写代码。
888
889```typescript
890class Person {
891  name?: string; // 可能为`undefined`
892
893  setName(n:string): void {
894    this.name = n;
895  }
896
897  // 编译时错误:name可以是"undefined",所以这个API的返回值类型不能仅定义为string类型
898  getNameWrong(): string {
899    return this.name;
900  }
901
902  getName(): string | undefined { // 返回类型匹配name的类型
903    return this.name;
904  }
905}
906
907let jack = new Person();
908// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
909
910// 编译时错误:编译器认为下一行代码有可能会访问undefined的属性,报错
911jack.getName().length;  // 编译失败
912
913jack.getName()?.length; // 编译成功,没有运行时错误
914```
915
916#### getter和setter
917
918setter和getter可用于提供对对象属性的受控访问。
919
920在以下示例中,setter用于禁止将`_age`属性设置为无效值:
921
922```typescript
923class Person {
924  name: string = '';
925  private _age: number = 0;
926  get age(): number { return this._age; }
927  set age(x: number) {
928    if (x < 0) {
929      throw Error('Invalid age argument');
930    }
931    this._age = x;
932  }
933}
934
935let p = new Person();
936p.age; // 输出0
937p.age = -42; // 设置无效age值会抛出错误
938```
939
940在类中可以定义getter或者setter。
941
942### 方法
943
944方法属于类。类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。
945
946#### 实例方法
947
948以下示例说明了实例方法的工作原理。
949
950`calculateArea`方法通过将高度乘以宽度来计算矩形的面积:
951
952```typescript
953class RectangleSize {
954  private height: number = 0;
955  private width: number = 0;
956  constructor(height: number, width: number) {
957    this.height = height;
958    this.width = width;
959  }
960  calculateArea(): number {
961    return this.height * this.width;
962  }
963}
964```
965
966必须通过类的实例调用实例方法:
967
968```typescript
969let square = new RectangleSize(10, 10);
970square.calculateArea(); // 输出:100
971```
972
973#### 静态方法
974
975使用关键字`static`将方法声明为静态。静态方法属于类本身,只能访问静态字段。
976
977静态方法定义了类作为一个整体的公共行为。
978
979必须通过类名调用静态方法:
980
981```typescript
982class Cl {
983  static staticMethod(): string {
984    return 'this is a static method.';
985  }
986}
987console.log(Cl.staticMethod());
988```
989
990#### 继承
991
992一个类可以继承另一个类(称为基类),并使用以下语法实现多个接口:
993
994```typescript
995class [extends BaseClassName] [implements listOfInterfaces] {
996  // ...
997}
998```
999
1000继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。
1001
1002基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。
1003
1004示例:
1005
1006```typescript
1007class Person {
1008  name: string = '';
1009  private _age = 0;
1010  get age(): number {
1011    return this._age;
1012  }
1013}
1014class Employee extends Person {
1015  salary: number = 0;
1016  calculateTaxes(): number {
1017    return this.salary * 0.42;
1018  }
1019}
1020```
1021
1022包含`implements`子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。
1023
1024```typescript
1025interface DateInterface {
1026  now(): string;
1027}
1028class MyDate implements DateInterface {
1029  now(): string {
1030    // 在此实现
1031    return 'now';
1032  }
1033}
1034```
1035
1036#### 父类访问
1037
1038关键字`super`可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口:
1039
1040```typescript
1041class RectangleSize {
1042  protected height: number = 0;
1043  protected width: number = 0;
1044
1045  constructor (h: number, w: number) {
1046    this.height = h;
1047    this.width = w;
1048  }
1049
1050  draw() {
1051    /* 绘制边界 */
1052  }
1053}
1054class FilledRectangle extends RectangleSize {
1055  color = ''
1056  constructor (h: number, w: number, c: string) {
1057    super(h, w); // 父类构造函数的调用
1058    this.color = c;
1059  }
1060
1061  draw() {
1062    super.draw(); // 父类方法的调用
1063    // super.height -可在此处使用
1064    /* 填充矩形 */
1065  }
1066}
1067```
1068
1069#### 方法重写
1070
1071子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
1072
1073```typescript
1074class RectangleSize {
1075  // ...
1076  area(): number {
1077    // 实现
1078    return 0;
1079  }
1080}
1081class Square extends RectangleSize {
1082  private side: number = 0;
1083  area(): number {
1084    return this.side * this.side;
1085  }
1086}
1087```
1088
1089#### 方法重载签名
1090
1091通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。
1092
1093```typescript
1094class C {
1095  foo(x: number): void;            /* 第一个签名 */
1096  foo(x: string): void;            /* 第二个签名 */
1097  foo(x: number | string): void {  /* 实现签名 */
1098  }
1099}
1100let c = new C();
1101c.foo(123);     // OK,使用第一个签名
1102c.foo('aa'); // OK,使用第二个签名
1103```
1104
1105如果两个重载签名的名称和参数列表均相同,则为错误。
1106
1107### 构造函数
1108
1109类声明可以包含用于初始化对象状态的构造函数。
1110
1111构造函数定义如下:
1112
1113```typescript
1114constructor ([parameters]) {
1115  // ...
1116}
1117```
1118
1119如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数,例如:
1120
1121```typescript
1122class Point {
1123  x: number = 0;
1124  y: number = 0;
1125}
1126let p = new Point();
1127```
1128
1129在这种情况下,默认构造函数使用字段类型的默认值来初始化实例中的字段。
1130
1131#### 派生类的构造函数
1132
1133构造函数函数体的第一条语句可以使用关键字`super`来显式调用直接父类的构造函数。
1134
1135```typescript
1136class RectangleSize {
1137  constructor(width: number, height: number) {
1138    // ...
1139  }
1140}
1141class Square extends RectangleSize {
1142  constructor(side: number) {
1143    super(side, side);
1144  }
1145}
1146```
1147
1148#### 构造函数重载签名
1149
1150我们可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的构造函数头,构造函数实现紧随其后。
1151
1152```typescript
1153class C {
1154  constructor(x: number)             /* 第一个签名 */
1155  constructor(x: string)             /* 第二个签名 */
1156  constructor(x: number | string) {  /* 实现签名 */
1157  }
1158}
1159let c1 = new C(123);      // OK,使用第一个签名
1160let c2 = new C('abc');    // OK,使用第二个签名
1161```
1162
1163如果两个重载签名的名称和参数列表均相同,则为错误。
1164
1165### 可见性修饰符
1166
1167类的方法和属性都可以使用可见性修饰符。
1168
1169可见性修饰符包括:`private`、`protected`和`public`。默认可见性为`public`。
1170
1171#### Public(公有)
1172
1173`public`修饰的类成员(字段、方法、构造函数)在程序的任何可访问该类的地方都是可见的。
1174
1175#### Private(私有)
1176
1177`private`修饰的成员不能在声明该成员的类之外访问,例如:
1178
1179```typescript
1180class C {
1181  public x: string = '';
1182  private y: string = '';
1183  set_y (new_y: string) {
1184    this.y = new_y; // OK,因为y在类本身中可以访问
1185  }
1186}
1187let c = new C();
1188c.x = 'a'; // OK,该字段是公有的
1189c.y = 'b'; // 编译时错误:'y'不可见
1190```
1191
1192#### Protected(受保护)
1193
1194`protected`修饰符的作用与`private`修饰符非常相似,不同点是`protected`修饰的成员允许在派生类中访问,例如:
1195
1196```typescript
1197class Base {
1198  protected x: string = '';
1199  private y: string = '';
1200}
1201class Derived extends Base {
1202  foo() {
1203    this.x = 'a'; // OK,访问受保护成员
1204    this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的
1205  }
1206}
1207```
1208
1209### 对象字面量
1210
1211对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替`new`表达式。
1212
1213对象字面量的表示方式是:封闭在花括号对({})中的'属性名:值'的列表。
1214
1215```typescript
1216class C {
1217  n: number = 0;
1218  s: string = '';
1219}
1220
1221let c: C = {n: 42, s: 'foo'};
1222```
1223
1224ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:
1225
1226```typescript
1227class C {
1228  n: number = 0;
1229  s: string = '';
1230}
1231
1232function foo(c: C) {}
1233
1234let c: C
1235
1236c = {n: 42, s: 'foo'};  // 使用变量的类型
1237foo({n: 42, s: 'foo'}); // 使用参数的类型
1238
1239function bar(): C {
1240  return {n: 42, s: 'foo'}; // 使用返回类型
1241}
1242```
1243
1244也可以在数组元素类型或类字段类型中使用:
1245
1246```typescript
1247class C {
1248  n: number = 0;
1249  s: string = '';
1250}
1251let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}];
1252```
1253
1254#### `Record`类型的对象字面量
1255
1256泛型`Record<K, V>`用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值:
1257
1258```typescript
1259let map: Record<string, number> = {
1260  'John': 25,
1261  'Mary': 21,
1262}
1263
1264map['John']; // 25
1265```
1266
1267类型`K`可以是字符串类型或数值类型,而`V`可以是任何类型。
1268
1269```typescript
1270interface PersonInfo {
1271  age: number;
1272  salary: number;
1273}
1274let map: Record<string, PersonInfo> = {
1275  'John': { age: 25, salary: 10},
1276  'Mary': { age: 21, salary: 20}
1277}
1278```
1279
1280### 抽象类
1281
1282带有修饰符abstract的类称为抽象类。抽象类可用于表示一组更具体的概念所共有的概念。
1283
1284如果尝试创建抽象类的实例,则会发生编译时的错误:
1285
1286```typescript
1287abstract class X {
1288  field: number;
1289  constructor(p: number) {
1290    this.field = p;
1291  }
1292}
1293
1294let x = new X(666)  //编译时错误:不能创建抽象类的具体实例
1295```
1296
1297抽象类的子类可以是抽象类也可以是非抽象类。抽象父类的非抽象子类可以实例化。因此,执行抽象类的构造函数和该类非静态字段的字段初始化器:
1298
1299```typescript
1300abstract class Base {
1301  field: number;
1302  constructor(p: number) {
1303    this.field = p;
1304  }
1305}
1306
1307class Derived extends Base {
1308  constructor(p: number) {
1309    super(p);
1310  }
1311}
1312```
1313
1314#### 抽象方法
1315
1316带有abstract修饰符的方法称为抽象方法,抽象方法可以被声明但不能被实现。
1317
1318只有抽象类内才能有抽象方法,如果非抽象类具有抽象方法,则会发生编译时错误:
1319
1320```typescript
1321class Y {
1322  abstract method(p: string)  //编译时错误:抽象方法只能在抽象类内。
1323}
1324```
1325
1326## 接口
1327
1328接口声明引入新类型。接口是定义代码协定的常见方式。
1329
1330任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
1331
1332接口通常包含属性和方法的声明
1333
1334示例:
1335
1336```typescript
1337interface Style {
1338  color: string; // 属性
1339}
1340interface AreaSize {
1341  calculateAreaSize(): number; // 方法的声明
1342  someMethod(): void;     // 方法的声明
1343}
1344```
1345
1346实现接口的类示例:
1347
1348```typescript
1349// 接口:
1350interface AreaSize {
1351  calculateAreaSize(): number; // 方法的声明
1352  someMethod(): void;     // 方法的声明
1353}
1354
1355// 实现:
1356class RectangleSize implements AreaSize {
1357  private width: number = 0;
1358  private height: number = 0;
1359  someMethod(): void {
1360    console.log('someMethod called');
1361  }
1362  calculateAreaSize(): number {
1363    this.someMethod(); // 调用另一个方法并返回结果
1364    return this.width * this.height;
1365  }
1366}
1367```
1368
1369### 接口属性
1370
1371接口属性可以是字段、getter、setter或getter和setter组合的形式。
1372
1373属性字段只是getter/setter对的便捷写法。以下表达方式是等价的:
1374
1375```typescript
1376interface Style {
1377  color: string;
1378}
1379```
1380
1381```typescript
1382interface Style {
1383  get color(): string;
1384  set color(x: string);
1385}
1386```
1387
1388实现接口的类也可以使用以下两种方式:
1389
1390```typescript
1391interface Style {
1392  color: string;
1393}
1394
1395class StyledRectangle implements Style {
1396  color: string = '';
1397}
1398```
1399
1400```typescript
1401interface Style {
1402  color: string;
1403}
1404
1405class StyledRectangle implements Style {
1406  private _color: string = '';
1407  get color(): string { return this._color; }
1408  set color(x: string) { this._color = x; }
1409}
1410```
1411
1412### 接口继承
1413
1414接口可以继承其他接口,如下面的示例所示:
1415
1416```typescript
1417interface Style {
1418  color: string;
1419}
1420
1421interface ExtendedStyle extends Style {
1422  width: number;
1423}
1424```
1425
1426继承接口包含被继承接口的所有属性和方法,还可以添加自己的属性和方法。
1427
1428
1429### 抽象类和接口
1430
1431抽象类与接口都无法实例化。抽象类是类的抽象,抽象类用来捕捉子类的通用特性,接口是行为的抽象。在ArkTS中抽象类与接口的区别如下:
1432
1433* 一个类只能继承一个抽象类,而一个类可以实现一个或多个接口;
1434* 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
1435* 抽象类里面可以有方法的实现,但是接口完全都是抽象的,不存在方法的实现;
1436* 抽象类可以有构造函数,而接口不能有构造函数。
1437
1438## 泛型类型和函数
1439
1440泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
1441
1442### 泛型类和接口
1443
1444类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数`Element`:
1445
1446```typescript
1447class CustomStack<Element> {
1448  public push(e: Element):void {
1449    // ...
1450  }
1451}
1452```
1453
1454要使用类型CustomStack,必须为每个类型参数指定类型实参:
1455
1456```typescript
1457let s = new CustomStack<string>();
1458s.push('hello');
1459```
1460
1461编译器在使用泛型类型和函数时会确保类型安全。参见以下示例:
1462
1463```typescript
1464let s = new CustomStack<string>();
1465s.push(55); // 将会产生编译时错误
1466```
1467
1468### 泛型约束
1469
1470泛型类型的类型参数可以被限制只能取某些特定的值。例如,`MyHashMap<Key, Value>`这个类中的`Key`类型参数必须具有`hash`方法。
1471
1472```typescript
1473interface Hashable {
1474  hash(): number;
1475}
1476class MyHashMap<Key extends Hashable, Value> {
1477  public set(k: Key, v: Value) {
1478    let h = k.hash();
1479    // ...其他代码...
1480  }
1481}
1482```
1483
1484在上面的例子中,`Key`类型扩展了`Hashable`,`Hashable`接口的所有方法都可以为key调用。
1485
1486### 泛型函数
1487
1488使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
1489
1490```typescript
1491function last(x: number[]): number {
1492  return x[x.length - 1];
1493}
1494last([1, 2, 3]); // 3
1495```
1496
1497如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
1498
1499```typescript
1500function last<T>(x: T[]): T {
1501  return x[x.length - 1];
1502}
1503```
1504
1505现在,该函数可以与任何数组一起使用。
1506
1507在函数调用中,类型实参可以显式或隐式设置:
1508
1509```typescript
1510// 显式设置的类型实参
1511last<string>(['aa', 'bb']);
1512last<number>([1, 2, 3]);
1513
1514// 隐式设置的类型实参
1515// 编译器根据调用参数的类型来确定类型实参
1516last([1, 2, 3]);
1517```
1518
1519### 泛型默认值
1520
1521泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。
1522
1523```typescript
1524class SomeType {}
1525interface Interface <T1 = SomeType> { }
1526class Base <T2 = SomeType> { }
1527class Derived1 extends Base implements Interface { }
1528// Derived1在语义上等价于Derived2
1529class Derived2 extends Base<SomeType> implements Interface<SomeType> { }
1530
1531function foo<T = number>(): T {
1532  // ...
1533}
1534foo();
1535// 此函数在语义上等价于下面的调用
1536foo<number>();
1537```
1538
1539## 空安全
1540
1541默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(`strictNullChecks`),但规则更严格。
1542
1543在下面的示例中,所有行都会导致编译时错误:
1544
1545```typescript
1546let x: number = null;    // 编译时错误
1547let y: string = null;    // 编译时错误
1548let z: number[] = null;  // 编译时错误
1549```
1550
1551可以为空值的变量定义为联合类型`T | null`。
1552
1553```typescript
1554let x: number | null = null;
1555x = 1;    // ok
1556x = null; // ok
1557if (x != null) { /* do something */ }
1558```
1559
1560### 非空断言运算符
1561
1562后缀运算符`!`可用于断言其操作数为非空。
1563
1564应用于可空类型的值时,它的编译时类型变为非空类型。例如,类型将从`T | null`更改为`T`:
1565
1566```typescript
1567class A {
1568  value: number = 0;
1569}
1570
1571function foo(a: A | null) {
1572  a.value;   // 编译时错误:无法访问可空值的属性
1573  a!.value;  // 编译通过,如果运行时a的值非空,可以访问到a的属性;如果运行时a的值为空,则发生运行时异常
1574}
1575```
1576
1577### 空值合并运算符
1578
1579空值合并二元运算符`??`用于检查左侧表达式的求值是否等于`null`或者`undefined`。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
1580
1581换句话说,`a ?? b`等价于三元运算符`(a != null && a != undefined) ? a : b`。
1582
1583在以下示例中,`getNick`方法如果设置了昵称,则返回昵称;否则,返回空字符串:
1584
1585```typescript
1586class Person {
1587  // ...
1588  nick: string | null = null;
1589  getNick(): string {
1590    return this.nick ?? '';
1591  }
1592}
1593```
1594
1595### 可选链
1596
1597在访问对象属性时,如果该属性是`undefined`或者`null`,可选链运算符会返回`undefined`。
1598
1599```typescript
1600class Person {
1601  nick: string | null = null;
1602  spouse?: Person
1603
1604  setSpouse(spouse: Person): void {
1605    this.spouse = spouse;
1606  }
1607
1608  getSpouseNick(): string | null | undefined {
1609    return this.spouse?.nick;
1610  }
1611
1612  constructor(nick: string) {
1613    this.nick = nick;
1614    this.spouse = undefined;
1615  }
1616}
1617```
1618
1619**说明**:`getSpouseNick`的返回类型必须为`string | null | undefined`,因为该方法可能返回`null`或者`undefined`。
1620
1621可选链可以任意长,可以包含任意数量的`?.`运算符。
1622
1623在以下示例中,如果一个`Person`的实例有不为空的`spouse`属性,且`spouse`有不为空的`nick`属性,则输出`spouse.nick`。否则,输出`undefined`:
1624
1625```typescript
1626class Person {
1627  nick: string | null = null;
1628  spouse?: Person;
1629
1630  constructor(nick: string) {
1631    this.nick = nick;
1632    this.spouse = undefined;
1633  }
1634}
1635
1636let p: Person = new Person('Alice');
1637p.spouse?.nick; // undefined
1638```
1639
1640## 模块
1641
1642程序可划分为多组编译单元或模块。
1643
1644每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。
1645
1646与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。
1647
1648### 导出
1649
1650可以使用关键字`export`导出顶层的声明。
1651
1652未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
1653
1654```typescript
1655export class Point {
1656  x: number = 0;
1657  y: number = 0;
1658  constructor(x: number, y: number) {
1659    this.x = x;
1660    this.y = y;
1661  }
1662}
1663export let Origin = new Point(0, 0);
1664export function Distance(p1: Point, p2: Point): number {
1665  return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
1666}
1667```
1668
1669### 导入
1670
1671#### 静态导入
1672
1673导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:
1674
1675* 导入路径,用于指定导入的模块;
1676* 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。
1677
1678导入绑定可以有几种形式。
1679
1680假设模块具有路径“./utils”和导出实体“X”和“Y”。
1681
1682导入绑定`* as A`表示绑定名称“A”,通过`A.name`可访问从导入路径指定的模块导出的所有实体:
1683
1684```typescript
1685import * as Utils from './utils';
1686Utils.X // 表示来自Utils的X
1687Utils.Y // 表示来自Utils的Y
1688```
1689
1690导入绑定`{ ident1, ..., identN }`表示将导出的实体与指定名称绑定,该名称可以用作简单名称:
1691
1692```typescript
1693import { X, Y } from './utils';
1694X // 表示来自utils的X
1695Y // 表示来自utils的Y
1696```
1697
1698如果标识符列表定义了`ident as alias`,则实体`ident`将绑定在名称`alias`下:
1699
1700```typescript
1701import { X as Z, Y } from './utils';
1702Z // 表示来自Utils的X
1703Y // 表示来自Utils的Y
1704X // 编译时错误:'X'不可见
1705```
1706
1707#### 动态导入
1708应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。
1709import()语法通常称为动态导入(dynamic import),是一种类似函数的表达式,用来动态导入模块。以这种方式调用,将返回一个promise。
1710如下例所示,import(modulePath)可以加载模块并返回一个promise,该promise resolve为一个包含其所有导出的模块对象。该表达式可以在代码中的任意位置调用。
1711
1712```typescript
1713// Calc.ts
1714export function add(a:number, b:number):number {
1715  let c = a + b;
1716  console.info('Dynamic import, %d + %d = %d', a, b, c);
1717  return c;
1718}
1719
1720// Index.ts
1721import("./Calc").then((obj: ESObject) => {
1722  console.info(obj.add(3, 5));
1723}).catch((err: Error) => {
1724  console.error("Module dynamic import error: ", err);
1725});
1726```
1727
1728如果在异步函数中,可以使用let module = await import(modulePath)。
1729
1730```typescript
1731// say.ts
1732export function hi() {
1733  console.log('Hello');
1734}
1735export function bye() {
1736  console.log('Bye');
1737}
1738```
1739
1740那么,可以像下面这样进行动态导入:
1741
1742```typescript
1743async function test() {
1744  let ns = await import('./say');
1745  let hi = ns.hi;
1746  let bye = ns.bye;
1747  hi();
1748  bye();
1749}
1750```
1751
1752更多的使用动态import的业务场景和使用实例见[动态import](../arkts-utils/arkts-dynamic-import.md)。
1753
1754<!--RP2--><!--RP2End-->
1755
1756### 顶层语句
1757
1758顶层语句是指在模块的最外层直接编写的语句,这些语句不被包裹在任何函数、类、块级作用域中。顶层语句包括变量声明、函数声明、表达式等。
1759
1760## 关键字
1761
1762### this
1763
1764关键字`this`只能在类的实例方法中使用。
1765
1766**示例**
1767
1768```typescript
1769class A {
1770  count: string = 'a';
1771  m(i: string): void {
1772    this.count = i;
1773  }
1774}
1775```
1776
1777使用限制:
1778
1779* 不支持`this`类型
1780* 不支持在函数和类的静态方法中使用`this`
1781
1782**示例**
1783
1784```typescript
1785class A {
1786  n: number = 0;
1787  f1(arg1: this) {} // 编译时错误,不支持this类型
1788  static f2(arg1: number) {
1789    this.n = arg1;  // 编译时错误,不支持在类的静态方法中使用this
1790  }
1791}
1792
1793function foo(arg1: number) {
1794  this.n = i;       // 编译时错误,不支持在函数中使用this
1795}
1796```
1797
1798关键字`this`的指向:
1799
1800* 调用实例方法的对象
1801* 正在构造的对象
1802
1803## ArkUI支持
1804
1805本节演示ArkTS为创建图形用户界面(GUI)程序提供的机制。ArkUI基于TypeScript提供了一系列扩展能力,以声明式地描述应用程序的GUI以及GUI组件间的交互。
1806
1807
1808### ArkUI示例
1809
1810[MVVM代码示例](arkts-mvvm.md#代码示例)提供了一个完整的基于ArkUI的应用程序,以展示其GUI编程功能。
1811
1812有关ArkUI功能的更多详细信息,请参见ArkUI[基本语法概述](arkts-basic-syntax-overview.md)。
1813