Harmony 入门笔记二(ArkTS基本)
即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。如果使用静态类型,那么程序中变量的类型就是确定的。同时,由于所有类型在程序实际运行前都是已知的,编译器可以验证代码的正确性,从而减少运行时的类型检查,有助于性能提升。类型,又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。每个模块都有其自己的作
初识ArkTS语言
ArkTS是OpenHarmony优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,保持了TS的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。
从API version 10开始,ArkTS进一步通过规范强化静态检查和分析,对比标准TS的差异可以参考从TypeScript到ArkTS的适配规则:
-
强制使用静态类型:静态类型是ArkTS最重要的特性之一。如果使用静态类型,那么程序中变量的类型就是确定的。同时,由于所有类型在程序实际运行前都是已知的,编译器可以验证代码的正确性,从而减少运行时的类型检查,有助于性能提升。
-
禁止在运行时改变对象布局:为实现最大性能,ArkTS要求在程序执行期间不能更改对象布局。
-
限制运算符语义:为获得更好的性能并鼓励开发者编写更清晰的代码,ArkTS限制了一些运算符的语义。比如,一元加法运算符只能作用于数字,不能用于其他类型的变量。
-
不支持Structural typing:对Structural typing的支持需要在语言、编译器和运行时进行大量的考虑和仔细的实现,当前ArkTS不支持该特性。根据实际场景的需求和反馈,我们后续会重新考虑。
基本知识
声明
ArkTS通过声明引入变量、常量、函数和类型。
语法:关键字 变量/常量名 :类型 = 值
变量声明
以关键字let
开头的声明引入变量,该变量在程序执行期间可以具有不同的值。
let hi: string = 'hello';
hi = 'hello, world';
常量声明
以关键字const
开头的声明引入只读常量,该常量只能被赋值一次。
const hi:string='你好';
对常量重新赋值会造成编译时错误。
自动类型推断
由于ArkTS是一种静态类型语言,所有数据的类型都必须在编译时确定。
但是,如果一个变量或常量的声明包含了初始值,那么开发者就不需要显式指定其类型。ArkTS规范中列举了所有允许自动推断类型的场景。
以下示例中,两条声明语句都是有效的,两个变量都是string
类型:
let hi1: string = 'hello';
let hi2 = 'hello, world';
类型
Number
类型
ArkTS提供number
和Number
类型,任何整数和浮点数都可以被赋给此类型的变量。
数字字面量包括整数字面量和十进制浮点数字面量。
整数字面量包括以下类别:
- 由数字序列组成的十进制整数。例如:
0
、117
、-345
- 以0x(或0X)开头的十六进制整数,可以包含数字(0-9)和字母a-f或A-F。例如:
0x1123
、0x00111
、-0xF1A7
- 以0o(或0O)开头的八进制整数,只能包含数字(0-7)。例如:
0o777
- 以0b(或0B)开头的二进制整数,只能包含数字0和1。例如:
0b11
、0b0011
、-0b11
浮点字面量包括以下:
- 十进制整数,可为有符号数(即,前缀为“+”或“-”);
- 小数点(“.”)
- 小数部分(由十进制数字字符串表示)
- 以“e”或“E”开头的指数部分,后跟有符号(即,前缀为“+”或“-”)或无符号整数。
示例:
let n1 = 3.14; let n2 = 3.141592; let n3 = .5; let n4 = 1e10; function factorial(n: number): number { if (n <= 1) { return 1; } return n * factorial(n - 1); }
Boolean
类型
boolean
类型由true
和false
两个逻辑值组成。
通常在条件语句中使用boolean
类型的变量:
let isDone: boolean = false;
// ...
if (isDone) {
console.log ('Done!');
}
String
类型
string
代表字符序列;可以使用转义字符来表示字符。
字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。
let s1 = 'Hello, world!\n';
let s2 = 'this is a string';
let a = 'Success';
let s3 = `The result is ${a}`;
Void
类型
void
类型用于指定函数没有返回值。 此类型只有一个值,同样是void
。由于void
是引用类型,因此它可以用于泛型类型参数。
class Class<T> { //... }
let instance: Class <void>
Object
类型
Object
类型是所有引用类型的基类型。任何值,包括基本类型的值(它们会被自动装箱),都可以直接被赋给Object
类型的变量。
Array
类型
array
,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。 数组可由数组复合字面量(即用方括号括起来的零个或多个表达式的列表,其中每个表达式为数组中的一个元素)来赋值。数组的长度由数组中元素的个数来确定。数组中第一个元素的索引为0。
以下示例将创建包含三个元素的数组:
let names: string[] = ['Alice', 'Bob', 'Carol'];
Enum
类型
enum
类型,又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。 使用枚举常量时必须以枚举类型名称为前缀。
enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Red;
常量表达式可以用于显式设置枚举常量的值。
enum ColorSet { White = 0xFF, Grey = 0x7F, Black = 0x00 }
let c: ColorSet = ColorSet.Black;
Union
类型
union
类型,即联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。
class Cat {
// ...
}
class Dog {
// ...
}
class Frog {
// ...
}
type Animal = Cat | Dog | Frog | number
// Cat、Dog、Frog是一些类型(类或接口)
let animal: Animal = new Cat();
animal = new Frog();
animal = 42;
// 可以将类型为联合类型的变量赋值为任何组成类型的有效值
可以用不同的机制获取联合类型中特定类型的值。 示例:
class Cat { sleep () {}; meow () {} }
class Dog { sleep () {}; bark () {} }
class Frog { sleep () {}; leap () {} }
type Animal = Cat | Dog | Frog | number
let animal: Animal = new Frog();
if (animal instanceof Frog) {
let frog: Frog = animal as Frog; // animal在这里是Frog类型
animal.leap();
frog.leap();
// 结果:青蛙跳了两次
}
animal.sleep (); // 任何动物都可以睡觉
Aliases
类型
Aliases
类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。
type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate <T> = (x: T) => Boolean;
type NullableObject = Object | null;
type
是一种创建类型别名的方式。类型别名可以用于简化复杂类型的书写,或者创建用户自定义的类型。例如:
type StringOrNumber = string | number;
let value: StringOrNumber;
value = 'hello'; // 这是合法的
value = 123;
运算符
赋值运算符
赋值运算符=
,使用方式如x=y
。
复合赋值运算符将赋值与运算符组合在一起,其中x op = y
等于x = x op y
。
复合赋值运算符列举如下:+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、>>>=
、&=
、|=
、^=
。
比较运算符
运算符 | 说明 |
---|---|
== |
如果两个操作数相等,则返回true。 |
!= |
如果两个操作数不相等,则返回true。 |
> |
如果左操作数大于右操作数,则返回true。 |
>= |
如果左操作数大于或等于右操作数,则返回true。 |
< |
如果左操作数小于右操作数,则返回true。 |
<= |
如果左操作数小于或等于右操作数,则返回true。 |
算术运算符
一元运算符为-
、+
、--
、++
。
二元运算符列举如下
运算符 | 说明 |
---|---|
+ |
加法 |
- |
减法 |
* |
乘法 |
/ |
除法 |
% |
除法后余数 |
位运算符
运算符 | 说明 |
---|---|
a & b |
按位与:如果两个操作数的对应位都为1,则将这个位设置为1,否则设置为0。 |
a | b |
按位或:如果两个操作数的相应位中至少有一个为1,则将这个位设置为1,否则设置为0。 |
a ^ b |
按位异或:如果两个操作数的对应位不同,则将这个位设置为1,否则设置为0。 |
~ a |
按位非:反转操作数的位。 |
a << b |
左移:将a的二进制表示向左移b位。 |
a >> b |
算术右移:将a的二进制表示向右移b位,带符号扩展。 |
a >>> b |
逻辑右移:将a的二进制表示向右移b位,左边补0。 |
逻辑运算符
运算符 | 说明 |
---|---|
a && b |
逻辑与 |
a || b |
逻辑或 |
! a |
逻辑非 |
If
语句
if
语句用于需要根据逻辑条件执行不同语句的场景。当逻辑条件为真时,执行对应的一组语句,否则执行另一组语句(如果有的话)。 else
部分也可能包含if
语句。
if
语句如下所示:
//condition代码判断条件别称
if (condition1) {
// 语句1 }
else if (condition2)
{ // 语句2 }
else { // else语句 }
Switch
语句
使用switch
语句来执行与switch
表达式值匹配的代码块。
switch
语句如下所示:
switch (expression) { case label1: // 如果label1匹配,则执行 // ... // 语句1 // ... break; // 可省略 case label2:
case label3: // 如果label2或label3匹配,则执行 // ... // 语句23 // ... break; // 可省略 default: // 默认语句 }
如果switch
表达式的值等于某个label的值,则执行相应的语句。
如果没有任何一个label值与表达式值相匹配,并且switch
具有default
子句,那么程序会执行default
子句对应的代码块。
break
语句(可选的)允许跳出switch
语句并继续执行switch
语句之后的语句。
如果没有break
语句,则执行switch
中的下一个label对应的代码块。
条件表达式
条件表达式(类似于java的三元表达式)由第一个表达式的布尔值来决定返回其它两个表达式中的哪一个。
示例如下:
condition ? expression1 : expression2
如果condition
的为真值(转换后为true
的值),则使用expression1
作为该表达式的结果;否则,使用expression2
。
let isValid = Math.random() > 0.5 ? true : false;
let message = isValid ? 'Valid' : 'Failed';
For
语句
for
语句会被重复执行,直到循环退出语句值为false
。
for
语句如下所示:
for ([init]; [condition]; [update]) {
statements }
for
语句的执行流程如下:
1、 执行init
表达式(如有)。此表达式通常初始化一个或多个循环计数器。
2、 计算condition
。如果它为真值(转换后为true
的值),则执行循环主体的语句。如果它为假值(转换后为false
的值),则for
循环终止。
3、 执行循环主体的语句。
4、 如果有update
表达式,则执行该表达式。
5、 回到步骤2。
示例:
let sum = 0;
for (let i = 0; i < 10; i += 2)
{
sum += i;
}
For-of
语句
使用for-of
语句可遍历数组或字符串。示例如下:
for (forVar of expression) {
statements
}
typescript
示例:
for (let ch of 'a string object') {
/* process ch */
}
typescript
While
语句
只要condition
为真值(转换后为true
的值),while
语句就会执行statements
语句。示例如下:
while (condition) {
statements
}
typescript
示例:
let n = 0;
let x = 0;
while (n < 3) {
n++;
x += n;
}
typescript
Do-while
语句
如果condition
的值为真值(转换后为true
的值),那么statements
语句会重复执行。示例如下:
do {
statements
} while (condition)
typescript
示例:
let i = 0;
do {
i += 1;
} while (i < 10)
typescript
Break
语句
使用break
语句可以终止循环语句或switch
。
示例:
let x = 0;
while (true) {
x++;
if (x > 5) {
break;
}
}
typescript
如果break
语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
示例:
let x = 1
label: while (true) {
switch (x) {
case 1:
// statements
break label // 中断while语句
}
}
typescript
Continue
语句
continue
语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。
示例:
let sum = 0;
for (let x = 0; x < 100; x++) {
if (x % 2 == 0) {
continue
}
sum += x;
}
typescript
Throw
和Try
语句
throw
语句用于抛出异常或错误:
throw new Error('this error')
typescript
try
语句用于捕获和处理异常或错误:
try {
// 可能发生异常的语句块
} catch (e) {
// 异常处理
}
typescript
下面的示例中throw
和try
语句用于处理除数为0的错误:
class ZeroDivisor extends Error {}
function divide (a: number, b: number): number{
if (b == 0) throw new ZeroDivisor();
return a / b;
}
function process (a: number, b: number) {
try {
let res = divide(a, b);
console.log('result: ' + res);
} catch (x) {
console.log('some error');
}
}
typescript
支持finally
语句:
function processData(s: string) {
let error: Error | null = null;
try {
console.log('Data processed: ' + s);
// ...
// 可能发生异常的语句
// ...
} catch (e) {
error = e as Error;
// ...
// 异常处理
// ...
} finally {
if (error != null) {
console.log(`Error caught: input='${s}', message='${error.message}'`);
}
}
}
typescript
函数
函数声明
函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。
以下示例是一个简单的函数,包含两个string
类型的参数,返回类型为string
:
function add(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}
typescript
在函数声明中,必须为每个参数标记类型。如果参数为可选参数,那么允许在调用函数时省略该参数。函数的最后一个参数可以是rest参数。
可选参数
可选参数的格式可为name?: Type
。
function hello(name?: string) {
if (name == undefined) {
console.log('Hello!');
} else {
console.log(`Hello, ${name}!`);
}
}
typescript
可选参数的另一种形式为设置的参数默认值。如果在函数调用中这个参数被省略了,则会使用此参数的默认值作为实参。
function multiply(n: number, coeff: number = 2): number {
return n * coeff;
}
multiply(2); // 返回2*2
multiply(2, 3); // 返回2*3
typescript
Rest参数
函数的最后一个参数可以是rest参数。使用rest参数时,允许函数或方法接受任意数量的实参。
function sum(...numbers: number[]): number {
let res = 0;
for (let n of numbers)
res += n;
return res;
}
sum() // 返回0
sum(1, 2, 3) // 返回6
typescript
返回类型
如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。
// 显式指定返回类型
function foo(): string { return 'foo'; }
// 推断返回类型为string
function goo() { return 'goo'; }
typescript
不需要返回值的函数的返回类型可以显式指定为void
或省略标注。这类函数不需要返回语句。
以下示例中两种函数声明方式都是有效的:
function hi1() { console.log('hi'); }
function hi2(): void { console.log('hi'); }
typescript
函数的作用域
函数中定义的变量和其他实例仅可以在函数内部访问,不能从外部访问。
如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。
函数调用
调用函数以执行其函数体,实参值会赋值给函数的形参。
如果函数定义如下:
function join(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}
typescript
则此函数的调用需要包含两个string
类型的参数:
let x = join('hello', 'world');
console.log(x);
typescript
函数类型
函数类型通常用于定义回调:
type trigFunc = (x: number) => number // 这是一个函数类型
function do_action(f: trigFunc) {
f(3.141592653589); // 调用函数
}
do_action(Math.sin); // 将函数作为参数传入
typescript
箭头函数或Lambda函数
函数可以定义为箭头函数,例如:
let sum = (x: number, y: number): number => {
return x + y;
}
typescript
箭头函数的返回类型可以省略;省略时,返回类型通过函数体推断。
表达式可以指定为箭头函数,使表达更简短,因此以下两种表达方式是等价的:
let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + y
typescript
闭包
闭包是由函数及声明该函数的环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
在下例中,z
是执行f
时创建的g
箭头函数实例的引用。g
的实例维持了对它的环境的引用(变量count
存在其中)。因此,当z
被调用时,变量count
仍可用。
function f(): () => number {
let count = 0;
let g = (): number => { count++; return count; };
return g;
}
let z = f();
z(); // 返回:1
z(); // 返回:2
typescript
函数重载
我们可以通过编写重载,指定函数的不同调用方式。具体方法为,为同一个函数写入多个同名但签名不同的函数头,函数实现紧随其后。
function foo(x: number): void; /* 第一个函数定义 */
function foo(x: string): void; /* 第二个函数定义 */
function foo(x: number | string): void { /* 函数实现 */
}
foo(123); // OK,使用第一个定义
foo('aa'); // OK,使用第二个定义
typescript
不允许重载函数有相同的名字以及参数列表,否则将会编译报错。
类
类声明引入一个新类型,并定义其字段、方法和构造函数。
在以下示例中,定义了Person
类,该类具有字段name
和surname
、构造函数和方法fullName
:
class Person {
name: string = ''
surname: string = ''
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname;
}
}
typescript
定义类后,可以使用关键字new
创建实例:
let p = new Person('John', 'Smith');
console.log(p.fullName());
typescript
或者,可以使用对象字面量创建实例:
class Point {
x: number = 0
y: number = 0
}
let p: Point = {x: 42, y: 42};
typescript
字段
字段是直接在类中声明的某种类型的变量。
类可以具有实例字段或者静态字段。
实例字段
实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。
要访问实例字段,需要使用类的实例。
class Person {
name: string = ''
age: number = 0
constructor(n: string, a: number) {
this.name = n;
this.age = a;
}
getName(): string {
return this.name;
}
}
let p1 = new Person('Alice', 25);
p1.name;
let p2 = new Person('Bob', 28);
p2.getName();
typescript
静态字段
使用关键字static
将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。
要访问静态字段,需要使用类名:
class Person {
static numberOfPersons = 0
constructor() {
// ...
Person.numberOfPersons++;
// ...
}
}
Person.numberOfPersons;
typescript
字段初始化
为了减少运行时的错误和获得更好的执行性能, ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization
模式一样。
以下代码是在ArkTS中不合法的代码。
class Person {
name: string // undefined
setName(n:string): void {
this.name = n;
}
getName(): string {
// 开发者使用"string"作为返回类型,这隐藏了name可能为"undefined"的事实。
// 更合适的做法是将返回类型标注为"string | undefined",以告诉开发者这个API所有可能的返回值。
return this.name;
}
}
let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack.getName().length; // 运行时异常:name is undefined
typescript
在ArkTS中,应该这样写代码。
class Person {
name: string = ''
setName(n:string): void {
this.name = n;
}
// 类型为'string',不可能为"null"或者"undefined"
getName(): string {
return this.name;
}
}
let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack.getName().length; // 0, 没有运行时异常
typescript
接下来的代码展示了如果name
的值可以是undefined
,那么应该如何写代码。
class Person {
name?: string // 可能为`undefined`
setName(n:string): void {
this.name = n;
}
// 编译时错误:name可以是"undefined",所以将这个API的返回值类型标记为string
getNameWrong(): string {
return this.name;
}
getName(): string | undefined { // 返回类型匹配name的类型
return this.name;
}
}
let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
// 编译时错误:编译器认为下一行代码有可能会访问undefined的属性,报错
jack.getName().length; // 编译失败
jack.getName()?.length; // 编译成功,没有运行时错误
typescript
getter和setter
setter和getter可用于提供对对象属性的受控访问。
在以下示例中,setter用于禁止将age
属性设置为无效值:
class Person {
name: string = ''
private _age: number = 0
get age(): number { return this._age; }
set age(x: number) {
if (x < 0) {
throw Error('Invalid age argument');
}
this._age = x;
}
}
let p = new Person();
p.age; // 输出0
p.age = -42; // 设置无效age值会抛出错误
typescript
在类中可以定义getter或者setter。
方法
方法属于类。类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。
实例方法
以下示例说明了实例方法的工作原理。
calculateArea
方法通过将高度乘以宽度来计算矩形的面积:
class RectangleSize {
private height: number = 0
private width: number = 0
constructor(height: number, width: number) {
// ...
}
calculateArea(): number {
return this.height * this.width;
}
}
typescript
必须通过类的实例调用实例方法:
let square = new RectangleSize(10, 10);
square.calculateArea(); // 输出:100
typescript
静态方法
使用关键字static
将方法声明为静态。静态方法属于类本身,只能访问静态字段。
静态方法定义了类作为一个整体的公共行为。
必须通过类名调用静态方法:
class Cl {
static staticMethod(): string {
return 'this is a static method.';
}
}
console.log(Cl.staticMethod());
typescript
继承
一个类可以继承另一个类(称为基类),并使用以下语法实现多个接口:
class [extends BaseClassName] [implements listOfInterfaces] {
// ...
}
typescript
继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。
基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。
示例:
class Person {
name: string = ''
private _age = 0
get age(): number {
return this._age;
}
}
class Employee extends Person {
salary: number = 0
calculateTaxes(): number {
return this.salary * 0.42;
}
}
typescript
包含implements
子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。
interface DateInterface {
now(): string;
}
class MyDate implements DateInterface {
now(): string {
// 在此实现
return 'now is now';
}
}
typescript
父类访问
关键字super
可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口:
class RectangleSize {
protected height: number = 0
protected width: number = 0
constructor (h: number, w: number) {
this.height = h;
this.width = w;
}
draw() {
/* 绘制边界 */
}
}
class FilledRectangle extends RectangleSize {
color = ''
constructor (h: number, w: number, c: string) {
super(h, w); // 父类构造函数的调用
this.color = c;
}
draw() {
super.draw(); // 父类方法的调用
// super.height -可在此处使用
/* 填充矩形 */
}
}
typescript
方法重写
子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
class RectangleSize {
// ...
area(): number {
// 实现
return 0;
}
}
class Square extends RectangleSize {
private side: number = 0
area(): number {
return this.side * this.side;
}
}
typescript
方法重载签名
通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后
class C {
foo(x: number): void; /* 第一个签名 */
foo(x: string): void; /* 第二个签名 */
foo(x: number | string): void { /* 实现签名 */
}
}
let c = new C();
c.foo(123); // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名
typescript
如果两个重载签名的名称和参数列表均相同,则为错误。
构造函数
类声明可以包含用于初始化对象状态的构造函数。
构造函数定义如
constructor ([parameters]) {
// ...
}
typescript
如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数,例如:
class Point {
x: number = 0
y: number = 0
}
let p = new Point();
typescript
在这种情况下,默认构造函数使用字段类型的默认值来初始化实例中的字段。
派生类的构造函数
构造函数函数体的第一条语句可以使用关键字super
来显式调用直接父类的构造函数
class RectangleSize {
constructor(width: number, height: number) {
// ...
}
}
class Square extends RectangleSize {
constructor(side: number) {
super(side, side);
}
}
typescript
构造函数重载签名
我们可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的
class C {
constructor(x: number) /* 第一个签名 */
constructor(x: string) /* 第二个签名 */
constructor(x: number | string) { /* 实现签名 */
}
}
let c1 = new C(123); // OK,使用第一个签名
let c2 = new C('abc'); // OK,使用第二个签名
typescript
如果两个重载签名的名称和参数列表均相同,则为错误。
可见性修饰符
类的方法和属性都可以使用可见性修饰符。
可见性修饰符包括:private
、protected
和public
。默认可见性为public
。
Public(公有)
public
修饰的类成员(字段、方法、构造函数)在程序的任何可访问该类的地方都是可见的。
Private(私有)
private
修饰的成员不能在声明该成员的类之外访问,例如:
class C {
public x: string = ''
private y: string = ''
set_y (new_y: string) {
this.y = new_y; // OK,因为y在类本身中可以访问
}
}
let c = new C();
c.x = 'a'; // OK,该字段是公有的
c.y = 'b'; // 编译时错误:'y'不可见
typescript
Protected(受保护)
protected
修饰符的作用与private
修饰符非常相似,不同点是protected
修饰的成员允许在派生类中访问,例如:
class Base {
protected x: string = ''
private y: string = ''
}
class Derived extends Base {
foo() {
this.x = 'a'; // OK,访问受保护成员
this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的
}
}
typescript
对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new
表达式。
对象字面量的表示方式是:封闭在花括号对({})中的’属性名:值’的列表。
class C {
n: number = 0
s: string = ''
}
let c: C = {n: 42, s: 'foo'};
typescript
ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:
class C {
n: number = 0
s: string = ''
}
function foo(c: C) {}
let c: C
c = {n: 42, s: 'foo'}; // 使用变量的类型
foo({n: 42, s: 'foo'}); // 使用参数的类型
function bar(): C {
return {n: 42, s: 'foo'}; // 使用返回类型
}
typescript
也可以在数组元素类型或类字段类型中使用:
class C {
n: number = 0
s: string = ''
}
let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}];
typescript
Record
类型的对象字面量
泛型Record<K, V>
用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值
let map: Record<string, number> = {
'John': 25,
'Mary': 21,
}
map['John']; // 25
typescript
类型K
可以是字符串类型或数值类型,而V
可以是任何类型。
interface PersonInfo {
age: number
salary: number
}
let map: Record<string, PersonInfo> = {
'John': { age: 25, salary: 10},
'Mary': { age: 21, salary: 20}
}
typescript
接口
接口声明引入新类型。接口是定义代码协定的常见方式。
任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
接口通常包含属性和方法的声明
示例:
interface Style {
color: string // 属性
}
interface AreaSize {
calculateAreaSize(): number // 方法的声明
someMethod(): void; // 方法的声明
}
typescript
实现接口的类示例:
接口属性
接口属性可以是字段、getter、setter或getter和setter组合的形式。
属性字段只是getter/setter对的便捷写法。以下表达方式是等价的:
interface Style {
color: string
}
typescrip
interface Style {
get color(): string
set color(x: string)
}
typescript
实现接口的类也可以使用以下两种方式:
interface Style {
color: string
}
class StyledRectangle implements Style {
color: string = ''
}
typescript
interface Style {
color: string
}
class StyledRectangle implements Style {
private _color: string = ''
get color(): string { return this._color; }
set color(x: string) { this._color = x; }
}
typescript
接口继承
接口可以继承其他接口,如下面的示例所示:
interface Style {
color: string
}
interface ExtendedStyle extends Style {
width: number
}
typescript
继承接口包含被继承接口的所有属性和方法,还可以添加自己的属性和方法。
泛型类型和函数
泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
泛型类和接口
类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数Element
:
class CustomStack<Element> {
public push(e: Element):void {
// ...
}
}
typescript
要使用类型CustomStack,必须为每个类型参数指定类型实参:
let s = new CustomStack<string>();
s.push('hello');
typescript
编译器在使用泛型类型和函数时会确保类型安全。参见以下示例:
let s = new CustomStack<string>();
s.push(55); // 将会产生编译时错误
typescript
泛型约束
泛型类型的类型参数可以绑定。例如,HashMap<Key, Value>
容器中的Key
类型参数必须具有哈希方法,即它应该是可哈希的。
interface Hashable {
hash(): number
}
class HasMap<Key extends Hashable, Value> {
public set(k: Key, v: Value) {
let h = k.hash();
// ...其他代码...
}
}
typescript
在上面的例子中,Key
类型扩展了Hashable
,Hashable
接口的所有方法都可以为key调用。
泛型函数
使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
function last(x: number[]): number {
return x[x.length - 1];
}
last([1, 2, 3]); // 3
typescript
如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
现在,该函数可以与任何数组一起使用。
在函数调用中,类型实参可以显式或隐式设置:
// 显式设置的类型实参
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]);
// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
last([1, 2, 3]);
typescript
泛型默认值
泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。
class SomeType {}
interface Interface <T1 = SomeType> { }
class Base <T2 = SomeType> { }
class Derived1 extends Base implements Interface { }
// Derived1在语义上等价于Derived2
class Derived2 extends Base<SomeType> implements Interface<SomeType> { }
function foo<T = number>(): T {
// ...
}
foo();
// 此函数在语义上等价于下面的调用
foo<number>();
typescript
空安全
默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(strictNullChecks
),但规则更严格。
在下面的示例中,所有行都会导致编译时错误:
let x: number = null; // 编译时错误
let y: string = null; // 编译时错误
let z: number[] = null; // 编译时错误
typescript
可以为空值的变量定义为联合类型T | null
。
let x: number | null = null;
x = 1; // ok
x = null; // ok
if (x != null) { /* do something */ }
typescript
非空断言运算符
后缀运算符!
可用于断言其操作数为非空。
应用于空值时,运算符将抛出错误。否则,值的类型将从T | null
更改为T
:
空值合并运算符
空值合并二元运算符??
用于检查左侧表达式的求值是否等于null。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
换句话说,a ?? b
等价于三元运算符a != null ? a : b
。
在以下示例中,getNick
方法如果设置了昵称,则返回昵称;否则,返回空字符串:
class Person {
// ...
nick: string | null = null
getNick(): string {
return this.nick ?? '';
}
}
typescript
可选链
在访问对象属性时,如果该属性是undefined
或者null
,可选链运算符会返回undefined
。
class Person {
nick: string | null = null
spouse?: Person
setSpouse(spouse: Person): void {
this.spouse = spouse;
}
getSpouseNick(): string | null | undefined {
return this.spouse?.nick;
}
constructor(nick: string) {
this.nick = nick;
this.spouse = undefined;
}
}
typescript
说明:getSpouseNick
的返回类型必须为string | null | undefined
,因为该方法可能返回null
或者undefined
。
可选链可以任意长,可以包含任意数量的?.
运算符。
在以下示例中,如果一个Person
的实例有不为空的spouse
属性,且spouse
有不为空的nickname
属性,则输出spouse.nick
。否则,输出undefined
:
class Person {
nick: string | null = null
spouse?: Person
constructor(nick: string) {
this.nick = nick;
this.spouse = undefined;
}
}
let p: Person = new Person('Alice');
p.spouse?.nick; // undefined
typescript
模块
程序可划分为多组编译单元或模块。
每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。
与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。
导出
可以使用关键字export
导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
注意:通过export方式导出,在导入时要加{}。
export class Point {
x: number = 0
y: number = 0
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export let Origin = new Point(0, 0);
export function Distance(p1: Point, p2: Point): number {
return Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
}
typescript
导入
导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:
- 导入路径,用于指定导入的模块;
- 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。
导入绑定可以有几种形式。
假设模块具有路径“./utils”和导出实体“X”和“Y”。
导入绑定* as A
表示绑定名称“A”,通过A.name
可访问从导入路径指定的模块导出的所有实体:
import * as Utils from './utils'
Utils.X // 表示来自Utils的X
Utils.Y // 表示来自Utils的Y
typescript
导入绑定{ ident1, ..., identN }
表示将导出的实体与指定名称绑定,该名称可以用作简单名称:
import { X, Y } from './utils'
X // 表示来自utils的X
Y // 表示来自utils的Y
typescript
如果标识符列表定义了ident as alias
,则实体ident
将绑定在名称alias
下:
import { X as Z, Y } from './utils'
Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见
typescript
顶层语句
模块可以包含除return语句外的任何模块级语句。
如果模块包含主函数(程序入口),则模块的顶层语句将在此函数函数体之前执行。否则,这些语句将在执行模块的其他功能之前执行。
程序入口
程序(应用)的入口是顶层主函数。主函数应具有空参数列表或只有string[]
类型的参数。
function main() {
console.log('this is the program entry');
}
typescript
更多推荐
所有评论(0)