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