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