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