变量
1
2
3
4
5
| let hi: string = 'hello'; //指定了类型
hi = 'hello, world'; //重新赋值
let hi2 = 'hello, world';//自动推断
const hello: string = 'hello'; //常量
|
基本数据类型包括number、string等简单类型,它们可以准确地表示单一的数据类型。直接访问
1
2
3
4
5
6
7
8
9
10
11
12
13
| let n1 = 3.14;
let n2 = 3.141592;
let n3 = .5;
let n4 = 1e2;
let bigInt: BigInt = BigInt('999999999999999999999999999999999999999999999999999999999999');
let isDone: boolean = false;
let s1 = 'Hello, world!\n';
let a = 'Success';
let s3 = `The result is ${a}`; //特殊形式,用反向单引号(`)括起来的模板字面量
|
void类型用于没有返回值的函数:
此类型只有一个值,同样是void。由于void是引用类型,因此它可以用于泛型类型参数。
1
2
3
4
| class Class<T> {
//...
}
let instance: Class <void>
|
Object类型是所有引用类型的基类型。任何值,包括基本类型的值,都可以直接被赋给Object类型的变量(基本类型值会被自动装箱)。
1
2
3
| let o1: Object = 'Alice';
let o2: Object = ['a','b'];
let o3: Object = 1;
|
array类型,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。
1
| let names: string[] = ['Alice', 'Bob', 'Carol'];
|
枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。
1
2
3
4
| enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Red;
enum ColorSet { White = 0xFF, Grey = 0x7F, Black = 0x00 } //显式设置值
|
Union类型,即联合类型,是由多个类型组合成的引用类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| class Cat {
name: string = 'cat';
// ...
}
class Dog {
name: string = 'dog';
// ...
}
class Frog {
name: string = 'frog';
// ...
}
type Animal = Cat | Dog | Frog | number | string | null | undefined;
// Cat、Dog、Frog是一些类型(类或接口)
let animal: Animal = new Cat();//Cat
animal = new Frog();
animal = 42;//number
animal = 'dog';//string
animal = undefined;
// 可以将类型为联合类型的变量赋值为任何组成类型的有效值
function foo(animal: Animal) {
if (animal instanceof Frog) {
animal.leap(); // animal在这里是Frog类型
}
animal.sleep(); // Animal具有sleep方法
}
|
Aliases类型为匿名类型(如数组、函数、对象字面量或联合类型)提供名称,或为已定义的类型提供替代名称。
1
2
3
4
| type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate <T> = (x: T) => boolean;
type NullableObject = Object | null;
|
运算符
赋值运算符
赋值运算符=,使用方式如x=y。
复合赋值运算符将赋值与运算符组合在一起,例如:a += b 等价于 a = a + b,
其中的 += 即为复合赋值运算符
复合赋值运算符包括:+=、-=、*=、/=、%=、«=、»=、»>=、&=、|=、^=。
比较运算符
值得注意的是下面这一组:
- === 如果两个操作数严格相等(对于不同类型的操作数认为是不相等的),则返回true。
- !== 如果两个操作数严格不相等(对于不同类型的操作数认为是不相等的),则返回true。
- == 如果两个操作数相等,则返回true。
- != 如果两个操作数不相等,则返回true。
语句
If
1
2
3
4
5
6
7
| if (condition1) {
// 语句1
} else if (condition2) {
// 语句2
} else {
// else语句
}
|
Switch
1
2
3
4
5
6
7
8
9
10
11
| switch (expression) {
case label1: // 如果label1匹配,则执行
// 语句1
break; // 可省略
case label2:
case label3: // 如果label2或label3匹配,则执行
// 语句23
break; // 可省略
default:
// 默认语句
}
|
三目运算符(条件表达式)
1
2
3
4
5
| condition ? expression1 : expression2
For
for ([init]; [condition]; [update]) {
statements
}
|
init,update部分可省略
For-of
使用for-of语句可遍历数组、Set、Map、字符串等可迭代的类型。示例如下:
1
2
3
4
5
6
7
| for (forVar of IterableExpression) {
// process forVar
}
for (let ch of 'a string object') {
console.info(ch);
}
|
While
1
2
3
| while (condition) {
statements
}
|
Do-while
1
2
3
| do {
statements
} while (condition)
|
循环控制
break和continue
异常处理
1
2
3
4
5
6
7
8
| try {
// 可能发生异常的语句块
throw new Error('this error')
} catch (e) {
// 异常处理
}finally {
// 无论是否发生异常都会执行的代码
}
|
函数
常规函数
1
2
3
4
| function add(x: string, y: string): string { //返回类型可以省略
let z: string = `${x} ${y}`;
return z;
}
|
1
2
3
4
5
6
| function hello(name?: string) { //可选参数
}
function multiply(n: number, coeff: number = 2): number { //给出默认值的参数
return n * coeff;
}
|
函数的最后一个参数可以是rest参数,格式为…restArgs。rest参数允许函数接收一个由剩余实参组成的数组,类型为任意指定类型,用于处理不定数量的参数输入。
1
2
3
4
5
6
| function sum(...numbers: number[]): number {
let res = 0;
for (let n of numbers)
res += n;
return res;
}
|
函数类型
函数类型通常用于定义回调函数:
1
2
3
4
5
6
7
| type trigFunc = (x: number) => number // 这是一个函数类型
function do_action(f: trigFunc) {
f(3.141592653589); // 调用函数
}
do_action(Math.sin); // 将函数作为参数传入
|
lambda函数
1
2
| let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + y
|
闭包
简单来说,闭包就是一个函数能够“记住”并访问其创建时所在的作用域(Scope)中的变量,即使这个函数在当前作用域之外被调用。
直观理解就是:函数和其相关的词法环境(Lexical Environment)的组合。这个“环境”包含了函数声明时所有可访问的局部变量。
1
2
3
4
5
6
7
8
9
| function f(): () => number {
let count = 0;
let g = (): number => { count++; return count; };
return g;
}
let z = f();
z(); // 返回:1
z(); // 返回:2
|
函数的重载
可以通过编写重载,指定函数的不同调用方式。具体方法是,为同一个函数写入多个同名但签名不同的函数头,函数实现紧随其后。
1
2
3
4
5
6
7
| function foo(x: number): void; /* 第一个函数定义 */
function foo(x: string): void; /* 第二个函数定义 */
function foo(x: number | string): void { /* 函数实现 */
}
foo(123); // OK,使用第一个定义
foo('aa'); // OK,使用第二个定义
|
类
基本结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| class Person {
name: string = '';
surname: string = '';
constructor (n: string, sn: string) { //构造函数
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname;
}
}
let p = new Person('John', 'Smith');
let p1 = new Person(name: 'John', surname: 'Smith')/
|
为了减少运行时错误并提升执行性能,ArkTS要求所有字段在声明时或构造函数中显式初始化,与标准TS的strictPropertyInitialization模式相同。
如果需要字段可以为空,则这么写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class Person {
name?: string; // 可能为`undefined`
setName(n:string): void {
this.name = n;
}
getName(): string | undefined { // 返回类型匹配name的类型
return this.name;
}
}
let jack = new Person();
// 假设代码中没有对name赋值,即没有调用"jack.setName('Jack')"
jack.getName()?.length; // 编译成功,没有运行时错误
|
使用this关键字访问对象内部资源
静态字段
要访问静态字段,需要使用类名
1
2
3
4
5
6
7
8
| class Person {
static numberOfPersons = 0;
constructor() {
// ...
}
}
Person.numberOfPersons;
|
getter和setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| 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值会抛出错误
|
静态方法
1
2
3
4
5
6
| class Cl {
static staticMethod(): string {
return 'this is a static method.';
}
}
console.info(Cl.staticMethod());
|
继承
1
2
3
| class [extends BaseClassName] [implements listOfInterfaces] {
// ...
}
|
关键字super可用于访问父类的实例字段、实例方法和构造函数。
对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。
1
2
3
4
5
6
7
8
9
10
11
12
13
| class C {
n: number = 0;
s: string = '';
}
let c: C = {n: 42, s: 'foo'};
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'}; // 使用返回类型
}
|
抽象类和抽象方法
带有abstract修饰符的类称为抽象类
1
2
3
4
5
6
| abstract class X {
field: number;
constructor(p: number) {
this.field = p;
}
}
|
带有abstract修饰符的方法称为抽象方法。只有抽象类内才能有抽象方法。
Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。
1
2
3
4
5
| let map: Record<string, number> = {
'John': 25,
'Mary': 21,
}
map['John']; // 25
|
类型K可以是字符串类型或数值类型(不包括bigint),而V可以是任何类型。
接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 接口:
interface AreaSize {
calculateAreaSize(): number; // 方法的声明
someMethod(): void; // 方法的声明
}
// 实现:
class RectangleSize implements AreaSize {
private width: number = 0;
private height: number = 0;
someMethod(): void {
console.info('someMethod called');
}
calculateAreaSize(): number {
this.someMethod(); // 调用另一个方法并返回结果
return this.width * this.height;
}
}
|
接口属性可以是字段、getter、setter或getter和setter组合的形式。属性字段只是getter/setter对的便捷写法。以下表达方式是等价的:
1
2
3
| interface Style {
color: string;
}
|
1
2
3
4
| interface Style {
get color(): string;
set color(x: string);
}
|
1
2
3
| class StyledRectangle implements Style {
color: string = '';
}
|
1
2
3
4
5
| class StyledRectangle implements Style {
private _color: string = '';
get color(): string { return this._color; }
set color(x: string) { this._color = x; }
}
|
泛型
类和接口可以定义为泛型,将参数添加到类型定义中。如以下示例中的类型参数Element:
1
2
3
4
5
6
7
8
| class CustomStack<Element> {
public push(e: Element):void {
// ...
}
}
let s = new CustomStack<string>();
s.push('hello');
|
泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。
1
2
3
4
5
| class MyHashMap<Key extends Hashable, Value> {
public set(k: Key, v: Value) {
// ...
}
}
|
函数也可以使用范型:
1
2
3
| function last<T>(x: T[]): T {
return x[x.length - 1];
}
|
1
2
3
4
5
6
| // 显式设置的类型实参
let res: string = last<string>(['aa', 'bb']);
let res: number = last<number>([1, 2, 3]);
// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
let res: number = last([1, 2, 3]);
|
范型可以设置默认值:
1
2
3
4
5
6
| function foo<T = number>(): void {
// ...
}
foo();
// 此函数在语义上等价于下面的调用
foo<number>();
|
空安全
默认情况下,ArkTS中的所有类型都不允许为空。
可以为空值的变量定义为联合类型T | null。
1
2
3
4
| let x: number | null = null;
x = 1; // ok
x = null; // ok
if (x != null) { /* do something */ }
|
非空断言运算符
后缀运算符!可用于断言其操作数为非空。(类似Dart)
1
2
3
4
5
6
| class A {
value: number = 0;
}
function foo(a: A | null) {
a!.value;
}
|
如果运行时a的值非空,可以访问到a的属性;
如果运行时a的值为空,则发生运行时异常
空值合并运算符
空值合并二元运算符??用于检查左侧表达式的求值是否等于null或者undefined。
如果是,则表达式的结果为右侧表达式;
否则,结果为左侧表达式。
换句话说,a ?? b等价于三元运算符(a != null && a != undefined) ? a : b。
可选链
用于在链式调用中阻止调用空对象的方法
如果spouse为空,则直接对整个表达式返回undefined
如果不为空,则继续(可选链只对它修饰的那个.生效)
模块
导出
可以使用关键字export导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
1
2
3
4
5
6
| export class Point {
}
export let Origin = new Point(0, 0);
export function Distance(p1: Point, p2: Point): number {
}
|
导入
支持静态绑定和动态绑定
1
2
3
4
5
6
7
8
9
10
| import * as Utils from './utils';
Utils.X // 表示来自Utils的X
Utils.Y // 表示来自Utils的Y
import { X, Y } from './utils';
X // 表示来自utils的X
Y // 表示来自utils的Y
import { X as Z, Y } from './utils';
Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见
|
动态导入
1
2
3
4
5
6
| // Calc.ts
export function add(a:number, b:number):number {
let c = a + b;
console.info('Dynamic import, %d + %d = %d', a, b, c);
return c;
}
|
1
2
3
4
5
6
| // Index.ts
import("./Calc").then((obj: ESObject) => {
console.info(obj.add(3, 5));
}).catch((err: Error) => {
console.error("Module dynamic import error: ", err);
});
|
详情见: 动态导入
注解
注解(Annotation)是一种语言特性,它通过添加元数据来改变应用声明的语义。
1
2
3
4
| // 注解的声明:
@interface ClassAuthor {
authorName: string
}
|
1
2
3
4
5
| // 注解的使用:
@ClassAuthor({authorName: "Bob"})
class MyClass {
// ...
}
|