文章目录
- 一、ArkTS语法学习
- 二、开发
-
- 1、应用开发准备
- 2、应用框架
-
- [1)Ability Kit(程序框架服务)](#1)Ability Kit(程序框架服务))
- [2)Accessibility Kit(无障碍服务)](#2)Accessibility Kit(无障碍服务))
- 3)ArkData(方舟数据管理)
- 4)ArkTS(方舟编程语言)
- 5)ArkUI(方舟UI框架)
- 6)ArkWeb(方舟web)
- [7)Background Tasks Kit(后台任务开发服务)](#7)Background Tasks Kit(后台任务开发服务))
- [8)Core File Kit(文件基础服务)](#8)Core File Kit(文件基础服务))
- [9)Form Kit(卡片开发服务)](#9)Form Kit(卡片开发服务))
- [10)IME Kit(输入法开发服务)](#10)IME Kit(输入法开发服务))
- [11)IPC Kit(进程间通信服务)](#11)IPC Kit(进程间通信服务))
- [12)Localization Kit(本地化开发服务)](#12)Localization Kit(本地化开发服务))
- [13)UI Design Kit(UI设计套件)](#13)UI Design Kit(UI设计套件))
一、ArkTS语法学习
ArkTS是鸿蒙生态的应用开发语言
1、初始ArkTS语言
ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript(简称TS)生态基础上做了进一步扩展,保持了TS的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能
2、ArkTS语言介绍
1)基础知识
①声明
-
变量声明
以关键字let 开头的声明引入变量,该变量在程序执行期间可以具有不同的值typescriptlet hi: string = 'hellow'; hi = 'hello,world';
-
常量声明
以关键字const 开头的声明引入只读常量,该常量只能被赋值一次typescriptconst hello: string = 'hellow';
对常量重新赋值会造成编译时错误
-
自动类型推断
由于ArkTS是一种静态类型语言,所有数据的类型都必须在编译时确定。
但是如果一个变量或常量的声明包含了初始值,那么开发者就不需要显示指定其类型。
以下实例中,两条声明语句都是有效的,两个变量都是string类型:
let hi1: string = 'hellow';
let hi2 = 'hellow,world';
②类型
-
Number类型
ArkTS提供number和Number类型,任何整数和浮点数都可以被赋给该类型的变量。
数字字面量包含数字面量和十进制浮点数字面量
整数字面量包含以下类型:
- 由数字序列组成的十进制整数。例如:0、117、-345
- 以0x或OX开头的十六进制整数,可以含数字(0-9)和字母a-f或A-F。例如0x1123、0x00111、-0xF1A7
- 以0o(或0O)开头的八进制整数,只能包含数字(0-7)。例如:0o777
- 以0b(或0B)开头的二进制整数,只能含数字0和1。例如 0b111、0b0011、-0b11
浮点字面量包含以下:
- 十进制,可为有符号数(即,前缀为"+"或"-")
- 小数点(".")
- 小数部分(由十进制数字字符串表示)
- 以"e"或"E"开头的指数部分
typescriptlet ni = 3.14; let n2 = 3.1415926; let n3 = .5; let n4 = le2;
-
Boolean类型
boolean 类型由 true 和 false 两个逻辑值组成
typescriptlet isDone: boolean = false; if(isDone){ console.log('Done!'); }
-
String类型
string 代表字符序列;可以使用转义字符来表示字符;
typescriptlet s1 = 'Hello, world!\n'; let s2 = 'this is a string'; let a = 'Success'; let s3 = `The result is ${a}`;
-
Void类型
void 类型用于指定函数没有返回值
此类型只有一个值,同样是void。由于void 是引用类型,因此他们可以用于泛型类型参数
typescriptclass Class<T> { } let instance: Class <void>
-
Object类型
Object类型是所有引用类型的基类型。任何值,包括基本类型的值(它们会被自动装箱),都可以直接被赋给Object类型的变量。object类型则用于表示除非基本类型外的类型。
-
Array类型
array,即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。
typescriptlet names: string[] = ['Alice', 'Bob', 'Carol'];
-
Enum类型
enum类型,又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。
使用枚举常量时必须以枚举类型名称为前缀。
typescriptenum ColorSet { Red, Green, Blue } let c: ColorSet = ColorSet.Red;
常量表达式可以用于显式设置枚举常量的值。
typescriptenum ColorSet { White = 0xFF, Grey = 0x7F, Black = 0x00 } let c: ColorSet = ColorSet.Black;
-
Union类型
union类型,即联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型
typescriptclass Cat { name: string = 'cat'; // ... } class Dog { name: string = 'dog'; // ... } class Frog { name: string = 'frog'; // ... } type Animal = Cat | Dog | Frog | number; // Cat、Dog、Frog是一些类型(类或接口) let animal: Animal = new Cat(); animal = new Frog(); animal = 42; // 可以将类型为联合类型的变量赋值为任何组成类型的有效值
可以用不同的机制获取联合类型中特定类型的值。
typescriptclass 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类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。
typescripttype Matrix = number[][]; type Handler = (s: string, no: number) => string; type Predicate <T> = (x: T) => boolean; type NullableObject = Object | null;
③运算符
-
赋值运算符
赋值运算符 = , 使用方式 如 x=y
复合赋值运算符将赋值与运算符组合在一起,其中 x op = y 等于 x = x op y
复合赋值运算符列举如下:+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、|=、^=。
-
比较运算符
运算符 说明 === 如果两个操作数严格相等(对于不同类型的操作数认为是不相等的),则返回true。 !== 如果两个操作数严格不相等(对于不同类型的操作数认为是不相等的),则返回true。 == 如果两个操作数相等,则返回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语句
typescriptif (condition1) { // 语句1 } else if (condition2) { // 语句2 } else { // else语句 }
条件表达式可以是任何类型。但是对于boolean以外的类型,会进行隐式类型转换:
typescriptlet s1 = 'Hello'; if (s1) { console.log(s1); // 打印"Hello" } let s2 = 'World'; if (s2.length != 0) { console.log(s2); // 打印"World" }
-
switch语句
typescriptswitch (expression) { case label1: // 如果label1匹配,则执行 // ... // 语句1 // ... break; // 可省略 case label2: case label3: // 如果label2或label3匹配,则执行 // ... // 语句23 // ... break; // 可省略 default: // 默认语句 }
-
条件表达式
typescriptlet isValid = Math.random() > 0.5 ? true : false; let message = isValid ? 'Valid' : 'Failed';
-
for语句
typescriptlet sum = 0; for (let i = 0; i < 10; i += 2) { sum += i; }
-
for-of语句
typescriptfor (let ch of 'a string object') { /* process ch */ }
-
while语句
typescriptlet n = 0; let x = 0; while (n < 3) { n++; x += n; }
-
do-while语句
typescriptlet i = 0; do { i += 1; } while (i < 10)
-
Break语句
使用break语句可以终止循环语句或switch。
typescriptlet x = 0; while (true) { x++; if (x > 5) { break; } }
如果break语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
typescriptlet x = 1; label: while (true) { switch (x) { case 1: // statements break label; // 中断while语句 } }
-
Continue语句
continue语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。
typescriptlet sum = 0; for (let x = 0; x < 100; x++) { if (x % 2 == 0) { continue; } sum += x; }
-
Throw 和 Try语句
throw语句用于抛出异常或错误:
typescriptthrow new Error('this error')
try语句用于捕获和处理异常或错误:
typescripttry { // 可能发生异常的语句块 } catch (e) { // 异常处理 }
2)函数
①函数声明
函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。
typescript
function add(x:string,y:string) : string{
let z : string = `${x} ${y}`
return z;
}
在函数声明中,必须为每个参数标记类型。如果参数为可选参数,那么允许在调用函数时省略该参数。函数的最后一个参数可以是rest参数。
②可选参数
可选参数的格式可为 name?:Type。
typescript
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
③Rest参数
函数的最后一个参数是rest参数。使用rest参数时,允许函数或方法接受任意数量的实参
typescript
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';}
function goo() {return 'goo';}
不需要返回值的函数的返回类型可以显式指定为void或省略标注。这类函数不需要返回语句。
typescript
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;
}
//函数的调用
let x = join('hello','world');
console.info(x);
⑦函数类型
函数类型通常用于定义回调:
typescript
type trigFunc = (x: number) => number // 这是一个函数类型
function do_action(f: trigFunc) {
f(3.141592653589); // 调用函数
}
do_action(Math.sin); // 将函数作为参数传入
⑧箭头函数(有名 Lambda函数)
typescript
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
function foo(x: number): void; /* 第一个函数定义 */
function foo(x: string): void; /* 第二个函数定义 */
function foo(x: number | string): void { /* 函数实现 */
}
foo(123); // OK,使用第一个定义
foo('aa'); // OK,使用第二个定义
3)类
类声明引入一个新类型,并定义其字段、方法和构造函数。
typescript
class Person {
name: string = '';
surname: string = '';
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname;
}
}
定义类后,可以使用关键字new创建实例:
typescript
let p = new Person('Join','Smith');
console.log(p.fullName());
或者,可以使用对象字面量创建实例:
typescript
class Point {
x: number = 0;
y: number = 0;
}
let p: Point = {x: 42, y: 42};
①字段
字段是直接在类中声明的某种类型的变量。
类可以具有实例字段或者静态字段。
-
实例字段
实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。
要访问实例字段,需要使用类的实例。
typescriptclass 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();
-
静态字段
使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。
要访问静态字段,需要使用类名:
typescriptclass Person { static numberOfPersons = 0; constructor() { // ... Person.numberOfPersons++; // ... } } Person.numberOfPersons;
-
字段初始化
为了减少运行时的错误和获得更好的执行性能,
ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization模式一样。
以下代码在ArkTS中不合法代码:
typescriptclass 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
在ArkTS中,应该这样写代码:
typescriptclass 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, 没有运行时异常
-
getter和setter
setter和getter可用于提供对对象属性的受控访问。
typescriptclass 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值会抛出错误
②方法
方法属于类。类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。
-
实例方法
先定义一个类:
typescriptclass RectangleSize { private height: number = 0; private width: number = 0; constructor(height: number, width: number) { this.height = height; this.width = width; } calculateArea(): number { return this.height * this.width; } }
必须通过类的实例调用实例方法:
typescriptlet square = new RectangleSize(10, 10); square.calculateArea(); // 输出:100
-
静态方法
使用关键字static将方法声明为静态。静态方法属于类本身,只能访问静态字段。
静态方法定义了类作为一个整体的公共行为。
必须通过类名调用静态方法:
typescriptclass C1{ statc statcMethod():string{ return 'tihs is a static method!'; } } concole.info(C1.statcMethod());
-
继承
一个类可以继承另一个类(成为基类),并使用以下语法实现多个接口:
typescriptclass [extends BaseClassName] [implements listOfInterface]{ //... }
继承类继承基类的字段和方法 ,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法
示例:
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; } }
包含implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。
typescriptinterface DateInterface { now(): string; } class MyDate implements DateInterface { now(): string { // 在此实现 return 'now'; } }
-
父类访问
关键字super可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口:
typescriptclass RectangleSize{ protected height:number = 0; protected width:number = 0; constructor(h:number,w:height){ 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 -可在此处使用 /* 填充矩形 */ } }
-
方法重写
子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
typescriptclass RectangleSize { // ... area(): number { // 实现 return 0; } } class Square extends RectangleSize { private side: number = 0; area(): number { return this.side * this.side; } }
-
方法重载签名
通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。
typescriptclass 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();
在这种情况下,默认构造函数使用字段类型的默认值来初始化实例中的字段。
-
派生类构造函数
构造函数函数体的第一条语句可以使用关键字super来显式调用直接父类的构造函数。typescriptclass RectangleSize { constructor(width: number, height: number) { // ... } } class Square extends RectangleSize { constructor(side: number) { super(side, side); //通过super显式调用父类的构造函数 } }
-
构造函数重载签名
我们可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的构造函数头,构造函数实现紧随其后。
typescriptclass C { constructor(x: number) /* 第一个签名 */ constructor(x: string) /* 第二个签名 */ constructor(x: number | string) { /* 实现签名 */ } } let c1 = new C(123); // OK,使用第一个签名 let c2 = new C('abc'); // OK,使用第二个签名
-
④可见性修饰符
类的方法和属性都可以使用可见性修饰符。
可见性修饰符包括:private 、protected 和public。默认可见性为public。
-
Public(共有)
public修饰的类成员(字段、方法、构造方法)在程序的任何可访问该类的地方都是可见的
-
Private(私有)
private修饰的成员不能在声明该成员的类之外访问,例如:
typescriptclass 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'不可见
-
Protected(受保护)
protected修饰符的作用与private修饰符非常相似,不同点是protected修饰的成员允许在派生类中访问,例如:
typescriptclass Base { protected x: string = ''; private y: string = ''; } class Derived extends Base { foo() { this.x = 'a'; // OK,访问受保护成员 this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的 } }
⑤对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。
对象字面量的表示方式是:封闭在花括号对({})中的'属性名:值'的列表。
typescript
class C {
n: number = 0;
s: string = '';
}
let c: C = {n: 42, s: 'foo'};
ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:
typescript
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'}];
-
Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值:
typescriptlet map: Record<string, number> = { 'John': 25, 'Mary': 21, } map['John']; // 25
类型K可以是字符串类型或数值类型,而V可以是任何类型。
typescriptinterface PersonInfo { age: number; salary: number; } let map: Record<string, PersonInfo> = { 'John': { age: 25, salary: 10}, 'Mary': { age: 21, salary: 20} }
⑥抽象类
带有修饰符abstract 的类称为抽象类。抽象类可用于表示一组更具体的概念所共有的概念。
如果尝试创建抽象类的实例,则会发生编译时的错误:
typescript
abstract class X {
field: number;
constructor(p: number) {
this.field = p;
}
}
let x = new X(666) //编译时错误:不能创建抽象类的具体实例
抽象类的子类可以是抽象类也可以是非抽象类。抽象父类的非抽象子类可以实例化。因此,执行抽象类的构造函数和该类非静态字段的字段初始化器:
typescript
abstract class Base {
field: number;
constructor(p: number) {
this.field = p;
}
}
class Derived extends Base {
constructor(p: number) {
super(p);
}
}
-
抽象方法
带有abstract修饰符的方法称为抽象方法,抽象方法可以被声明但不能被实现。
只有抽象类内才能有抽象方法,如果非抽象类具有抽象方法,则会发生编译时错误:
typescriptclass Y { abstract method(p: string) //编译时错误:抽象方法只能在抽象类内。 }
4)接口
接口声明引入新类型。接口是定义代码协定的常见方式。
任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
接口通常包含属性和方法的声明
typescript
interface Style {
color: string; // 属性
}
interface AreaSize {
calculateAreaSize(): number; // 方法的声明
someMethod(): void; // 方法的声明
}
实现接口的类示例:
typescript
// 接口:
interface AreaSize {
calculateAreaSize(): number; // 方法的声明
someMethod(): void; // 方法的声明
}
// 实现:
class RectangleSize implements AreaSize {
private width: number = 0;
private height: number = 0;
someMethod(): void {
console.log('someMethod called');
}
calculateAreaSize(): number {
this.someMethod(); // 调用另一个方法并返回结果
return this.width * this.height;
}
}
①接口属性
接口属性可以是字段、getter、setter或getter和setter组合的形式。
属性字段只是getter/setter对的便捷写法。以下表达方式是等价的:
typescript
interface Style {
color: string;
}
typescript
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;
}
继承接口包含被继承接口的所有属性和方法,还可以添加自己的属性和方法。
③抽象类和接口
抽象类与接口都无法实例化。抽象类是类的抽象,抽象类用来捕捉子类的通用特性,接口是行为的抽象。在ArkTS中抽象类与接口的区别如下:
- 一个类只能继承一个抽象类,而一个类可以实现一个或多个接口;
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
- 抽象类里面可以有方法的实现,但是接口完全都是抽象的,不存在方法的实现;
- 抽象类可以有构造函数,而接口不能有构造函数。
5)泛型类型和函数
泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
①泛型类和接口
类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数Element:
typescript
class CustomStack<Element> {
public push(e: Element):void {
// ...
}
}
要使用类型CustomStack,必须为每个类型参数指定类型实参:
typescript
let s = new CustomStack<string>();
s.push('hello');
编译器在使用泛型类型和函数时会确保类型安全。参见以下示例:
typescript
let s = new CustomStack<string>();
s.push(55); // 将会产生编译时错误
②泛型约束
泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。
typescript
interface Hashable {
hash(): number;
}
class MyHashMap<Key extends Hashable, Value> {
public set(k: Key, v: Value) {
let h = k.hash();
// ...其他代码...
}
}
在上面的例子中,Key类型扩展了Hashable,Hashable接口的所有方法都可以为key调用。
③泛型函数
使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
typescript
function last(x: number[]): number {
return x[x.length - 1];
}
last([1, 2, 3]); // 3
如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
typescript
function last<T>(x: T[]): T {
return x[x.length - 1];
}
在函数调用中,类型实参可以显式或隐式设置:
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>();
6)空安全
默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(strictNullChecks),但规则更严格。
在下面的示例中,所有行都会导致编译时错误:
typescript
let x: number = null; // 编译时错误
let y: string = null; // 编译时错误
let z: number[] = null; // 编译时错误
可以为空值的变量定义为联合类型T | null。
typescript
let x: number | null = null;
x = 1; // ok
x = null; // ok
if (x != null) { /* do something */ }
①非空断言运算符
后缀运算符!可用于断言其操作数为非空。
应用于可空类型的值时,它的编译时类型变为非空类型。例如,类型将从T | null更改为T:
typescript
class A {
value: number = 0;
}
function foo(a: A | null) {
a.value; // 编译时错误:无法访问可空值的属性
a!.value; // 编译通过,如果运行时a的值非空,可以访问到a的属性;如果运行时a的值为空,则发生运行时异常
}
②空值合并运算符
空值合并二元运算符??用于检查左侧表达式的求值是否等于null或者undefined。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。
换句话说,a ?? b等价于三元运算符(a != null && a != undefined) ? a : b。
在以下示例中,getNick方法如果设置了昵称,则返回昵称;否则,返回空字符串:
typescript
class Person {
// ...
nick: string | null = null;
getNick(): string {
return this.nick ?? '';
}
}
③可选链
在访问对象属性时,如果该属性是undefined或者null,可选链运算符会返回undefined。
typescript
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;
}
}
说明:getSpouseNick的返回类型必须为string | null | undefined,因为该方法可能返回null或者undefined。
可选链可以任意长,可以包含任意数量的?.运算符。
在以下示例中,如果一个Person的实例有不为空的spouse属性,且spouse有不为空的nick属性,则输出spouse.nick。否则,输出undefined:
typescript
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
7)模块
程序可划分为多组编译单元或模块。
每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。
与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。
①导出
可以使用关键字export导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
typescript
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));
}
②导入
-
静态导入
导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:
- 导入路径,用于指定导入的模块
- 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。
导入绑定可以有几种形式。
假设模块具有路径"./utils"和导出实体"X"和"Y"。、
导入绑定* as A表示绑定名称"A",通过A.name可访问从导入路径指定的模块导出的所有实体:
typescriptimport * as Utils from './utils' Utils.X // 表示来自Utils的X Utils.Y // 表示来自Utils的Y
导入绑定{ ident1, ..., identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:
typescriptimport { X, Y } from './utils' X // 表示来自utils的X Y // 表示来自utils的Y
如果标识符列表定义了ident as alias,则实体ident将绑定在名称alias下:
typescriptimport { X as Z, Y } from './utils' Z // 表示来自Utils的X Y // 表示来自Utils的Y X // 编译时错误:'X'不可见
-
动态导入
应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。
**import()**语法通常称为动态导入dynamic import,是一种类似函数的表达式,用来动态导入模块。以这种方式调用,将返回一个promise。
如下例所示,import(modulePath) 可以加载模块并返回一个promise,该promise resolve为一个包含其所有导出的模块对象。该表达式可以在代码中的任意位置调用。
typescript// 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; } // Index.ts import("./Calc").then((obj: ESObject) => { console.info(obj.add(3, 5)); }).catch((err: Error) => { console.error("Module dynamic import error: ", err); });
如果在异步函数中,可以使用let module = await import(modulePath)。
typescript// say.ts export function hi() { console.log('Hello'); } export function bye() { console.log('Bye'); }
那么,可以像下面这样进行动态导入:
typescriptasync function test() { let ns = await import('./say'); let hi = ns.hi; let bye = ns.bye; hi(); bye(); }
-
导入HarmonyOS SDK的开放能力
HarmonyOS SDK提供的开放能力(接口)也需要在导入声明后使用。可直接导入接口模块来使用该模块内的所有接口能力,例如:
typescriptimport UIAbility from '@ohos.app.ability.UIAbility';
从HarmonyOS NEXT Developer Preview 1版本开始引入Kit概念。SDK对同一个Kit下的接口模块进行了封装 ,开发者在示例代码中可通过导入Kit的方式来使用Kit所包含的接口能力。其中,Kit封装的接口模块可查看SDK目录下Kit子目录中各Kit的定义。
通过导入Kit方式使用开放能力有三种方式:
-
方式一:导入Kit下单个模块的接口能力。例如:
typescriptimport { UIAbility } from '@kit.AbilityKit';
-
方式二:导入Kit下多个模块的接口能力。例如:
typescriptimport { UIAbility, Ability, Context } from '@kit.AbilityKit';
-
方式三:导入Kit包含的所有模块的接口能力。例如:
typescriptimport * as module from '@kit.AbilityKit';
其中,"module"为别名,可自定义,然后通过该名称调用模块的接口。
说明:方式三可能会导入过多无需使用的模块,导致编译后的HAP包太大,占用过多资源,请谨慎使用。
-
③顶层语句
顶层语句是指在模块的最外层直接编写的语句,这些语句不被包裹在任何函数、类、块级作用域中。顶层语句包括变量声明、函数声明、表达式等。
8)关键字
①this
关键字this只能在类的实例方法中使用。
typescript
class A {
count: string = 'a';
m(i: string): void {
this.count = i;
}
}
使用限制:
- 不支持this类型
- 不支持在函数和类的静态方法中使用this
示例:
typescript
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
}
9)ArkUI支持
①ArkTS示例(MVVM)
二、开发
1、应用开发准备
1)注册开发者
在华为开发者联盟网站上,注册成为开发者 ,并完成实名认证,从而享受联盟开放的各类能力和服务。
2)创建应用
在AppGallery Connect (简称AGC)上,参考创建项目 和创建应用完成HarmonyOS应用的创建,从而使用各类服务
3)配置安装DevEco Studio
4)配置签名信息
使用模拟器和预览器调试无需配置签名信息,使用真机设备调试则需要对HAP进行签名。
目前提供两种签名方式,请根据实际情况选择:
- 自动签名:如果您只需要使用一台调试设备,建议使用DevEco Studio提供的自动签名。
- 手动签名:如果您使用多台调试设备或者会在断网情况下调试,您需要在AGC中申请调试证书、注册调试设备、申请调试Profile后,再手动配置签名信息。