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