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.ets的console.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