HarmonyOS Next应用开发:ArkTS语言介绍
通过介绍ArkTS的一些语法知识,使开发者能够使用ArkTS进行基本的开发。本文假设你有一定的编程经验,对于常见的语法,例如for、while不会过多介绍。
变量声明
以关键字let开头的声明引入变量,声明时数据类型要放到变量后面,并且用冒号“ : ”分隔,声明的变量在程序执行期间可以具有不同的值:
let str1:String = 'hello';
str1 = 'hello,world';
如果一个变量的声明包含了初始值,那么开发者就不需要显式指定其类型,会进行自动类型推断:
let str2 = 'hello, world';
常量声明
以关键字const开头的声明引入只读常量,该常量只能被赋值一次,如果一个常量的声明包含了初始值,同样会进行自动类型推断,对常量重新赋值会造成编译时错误:
const hello: string = 'hello';
类型
Number类型
在ArkTS中,无论是整数类型(如int, long)还是浮点类型(如float, double),都被统一视为number类型。
let n1:number = 119;
let n2:Number = 3.14;
let n3 = 0xF1A7
let n4 = 1e2;
Boolean类型
boolean类型由true和false两个逻辑值组成。
let isDone: boolean = false;
String类型
字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。
let s1 = 'Hello, world!\n';
let s2 = "this is a string";
let a = 'Success';
let s3 = `The result is ${a}`;
Void类型
void类型用于指定函数没有返回值。
function hello():void{
console.log("Hello");
}
Object类型
Object类型是所有引用类型的基类型。任何值,包括基本类型的值(它们会被自动装箱),都可以直接被赋给Object类型的变量。
let o: Object = false;
o= 1;
Array类型
// 通过字面量来创建数组
let names: string[] = ['Alice', 'Bob', 'Carol'];
// 没有元素,显式指定泛型参数为 string
let arr1: string[] = [];
arr1.push('Alice', 'Bob', 'Carol');
// 使用构造函数创建数组
let fruit:Array<string> = new Array('apple', 'banana');
let arr2:Array<string> = new Array();
arr2.push('apple');
arr2.push('banana');
//声明array是一个常量,不可以再指向另一个对象,但是可以给它添加元素
const array = [1,2,3];
// array1 = [1,2];//报错Cannot assign to 'array1' because it is a constant.
array.push(4);
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 {
// ...
}
// Cat、Dog、Frog是一些类型(类或接口)
type Animal = Cat | Dog | Frog | number
// 可以将类型为联合类型的变量赋值为任何组成类型的有效值
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
function foo(animal: Animal) {
if (animal instanceof Frog) {
animal.leap(); // animal在这里是Frog类型
}
animal.sleep(); // Animal具有sleep方法
}
Aliases类型
Aliases类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。
type Matrix = number[][];
type Handler = (s: string, no: number) => string;
type Predicate <T> = (x: T) => boolean;
type NullableObject = Object | null;
let matrix:Matrix = [[1,2],[3,4]];
语句
if语句
条件表达式可以是任何类型。但是对于boolean以外的类型,会进行隐式类型转换:
let s1 = 'Hello';
if (s1) {
console.log(s1); // 打印“Hello”
}
let s2 = 'World';
if (s2.length != 0) {
console.log(s2); // 打印“World”
}
let s3 = 0;
if (!s3) {
console.log(s3+""); // 打印“0”
}
let s4 = '';
if (!s4) {
console.log(s4+""); // 打印“”
}
let s5 = null;
if (!s5) {
console.log(s5+""); // 打印“null”
}
let s6 = undefined;
if (!s6) {
console.log(s6+""); // 打印“undefined”
}
for-of语句
使用for-of语句可遍历数组或字符串:
for (let ch of 'harmonyos') {
/* process ch */
}
Break语句
使用break语句可以终止循环语句或switch,如果break语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
let x = 1
label: while (true) {
switch (x) {
case 1:
// statements
break label; // 中断while语句
}
}
switch语句、条件表达式、for语句、while语句、do-while语句、continue语句、throw语句、try-catch语句和其它编程语言基本相似,此处不再过多介绍。
函数
函数声明
函数声明引入一个函数,使用function关键字开头,包含其名称、参数列表(参数类型要放到参数名称后面,并且用冒号“ : ”分隔)、返回类型(放到参数列表后面,并且用冒号“ : ”分隔;如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型;不需要返回值的函数的返回类型可以显式指定为void或省略标注。这类函数不需要返回语句。)和函数体。
以下示例是一个简单的函数,名称为add,包含两个string类型的参数,返回类型为string:
function add(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}
在函数声明中,必须为每个参数标记类型。如果参数为可选参数,那么允许在调用函数时省略该参数。函数的最后一个参数可以是rest参数。
可选参数
可选参数的格式可为name?: Type。
function hello(name?: string) {
if (name == undefined) {
console.log('Hello!');
} else {
console.log(`Hello, ${name}!`);
}
}
可选参数的另一种形式为设置的参数默认值。如果在函数调用中这个参数被省略了,则会使用此参数的默认值作为实参。
function multiply(n: number, coeff: number = 2): number {
return n * coeff;
}
multiply(2); // 返回2*2
multiply(2, 3); // 返回2*3
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
函数的作用域
函数中定义的变量和其他实例仅可以在函数内部访问,不能从外部访问。
如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。
类
类声明引入一个新类型,并定义其字段、方法和构造函数。
class Person {
// 实例字段
// 为了减少运行时的错误和获得更好的执行性能,
// ArkTS要求所有字段在声明时或者构造函数中显式初始化。
firstName: string = ''
lastName: string = ''
// 静态字段
private static numberOfPersons:= 0
// 构造函数用constructor 关键字声明
constructor (fn: string, ln: string) {
this.firstName = fn;
this.lastName = ln;
Person.numberOfPersons++;
}
// 实例方法
fullName(): string {
return this.firstName + ' ' + this.lastName;
}
// 静态方法
static getNumOfPersons():number {
return Person.numberOfPersons;
}
}
定义类后,可以使用关键字new创建实例:
let p = new Person('John', 'Smith');
或者,可以使用对象字面量创建实例:
let p:Person = {
firstName: 'John',
lastName: 'Smith',
fullName(): string {
return this.firstName+ ' ' + this.lastName;
}
};
getter和setter
setter和getter可用于提供对对象属性的受控访问,在类中可以定义getter或者setter。
在以下示例中,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值会抛出错误
在类的继承方面仍然是单继承,一个类可以继承另一个类。
类的方法和属性都可以使用可见性修饰符。可见性修饰符包括:private、protected和public。默认可见性为public。
接口
接口声明引入新类型。接口是定义代码协定的常见方式。任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。接口可以继承其他接口。
接口和其它语言中的使用相差无几,这里就不多赘述了。
Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值,类似于Map或字典的数据结构:
let map: Record<string, number> = {
'John': 25,
'Mary': 21,
}
map['John']; // 25
this
关键字this只能在类的实例方法中使用。
示例:
class A {
count: string = 'a'
m(i: string): void {
this.count = i;
}
}
使用限制:不支持this类型,不支持在函数和类的静态方法中使用this。
示例:
class A {
n: number = 0
f1(arg1: this) {} // 编译时错误,不支持this类型
static f2(arg1: number) {
this.n = arg1; // 编译时错误,不支持在类的静态方法中使用this
}
}
function foo(arg1: number) {
this.n = i; // 编译时错误,不支持在函数中使用this
}
关键字this的指向:调用实例方法的对象,正在构造的对象。