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