【HarmonyOS】HarmonyOS NEXT学习日记:二、ArkTs语法
众所周知TS是JS的超集,而ArkTs则可以理解为是Ts的超集。他们的基础都基于JS,所以学习之前最好就JS基础。我的学习重点也是放在ArkTs和JS的不同点上。
文章主要跟着官方文档学习,跳过了一些和js相同的地方,保留了一些js和ts不同的地方,同时在一些概念上添加了额外的解读和代码注释。方便ts基础不太扎实或者还没接触过的同学阅读。
声明
声明变量关键字和js一样,let、const
ArkTS是一种静态类型语言,所有数据的类型都必须在编译时确定,但是定义时有初始值则可以显式指定其类型。
let str: string = 'hello'; let str2= 'hello, world';
上面两种都是定义了string类型的变量。
类型
基本类型和js差不多,主要看看声明类型,和一些特殊的类型用法
String
字符串
let str: string = 'hello'; let str2:string= `The result is ${a}`;
Number
数字
let num: number= 1;
Boolean
布尔类型
let flag: boolean = false;
Void
void类型用于指定函数没有返回值。可以用于泛型类型参数。泛型是什么我们后面会提到。
Object
所有引用类型的基类型
Array
let values: number[] = [1, 2, 3];
Enum
枚举值
enum ColorSet { Red, Green, Blue } let c: ColorSet = ColorSet.Green; console.log(c)//2
enum ColorSet { White = '0xFF', Grey = '0x7F', Black = '0x00' } let c: ColorSet = ColorSet.Black; console.log(c)//0x00
Union类型
联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。
比如我想要一个数组既可以储存string也可以储存number就可以像下面这样:
type x = string | number; let arr: x[] = [1, '2'];
用type定义一个名为x的联合类型,然后使用他声明arr的类型即可。
Aliases
Aliases类型为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。
比如我希望把两层的数组类型多处复用,给这种类型命名Matrix,则可以像下面这样
type Matrix = (number|string)[][] let arr:Matrix = [[1,2,3],['4','5','6']]
运算符
和js基本一致,略过。想要详细了解的同学可以查看官方文档
语句
什么if else switch之类的用法和js一致,略过。想要详细了解的同学可以查看官方文档
函数
函数声明
函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。
以下示例是一个简单的函数,包含两个string类型的参数,返回类型为string:
function add(x: string, y: string): string { let z: string = `${x} ${y}`; return z; }
可选参数
可选参数的格式可为name?: Type
function hello(name?: string) { if (name == undefined) { console.log('undefined'); } else { console.log(name); } }
当然也可以设置参数缺省值
function hello(name: string = 'me') { console.log(name); } hello()//输出me
Rest参数
用法与js相同
Rest就是为解决传入的参数数量不一定, rest parameter(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
返回类型
如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。
// 显式指定返回类型 function foo(): string { return 'foo'; } // 推断返回类型为string function goo() { return 'goo'; }
不需要返回值的函数的返回类型可以显式指定为void或省略标注。这类函数不需要返回语句。
function hi1() { console.log('hi'); } function hi2(): void { console.log('hi'); }
以上两种均可。
函数类型
函数类型通常用于定义回调
type trigFunc = (x: number) => number // 这是一个函数类型 function do_action(f: trigFunc) { f(3.141592653589); // 调用函数 } do_action(Math.sin); // 将函数作为参数传入
箭头函数
let sum = (x: number, y: number): number => { return x + y; }
返回类型可以省略;省略时,返回类型通过函数体推断
函数重载
function foo(x: number): void; /* 第一个函数定义 */ function foo(x: string): void; /* 第二个函数定义 */ function foo(x: number | string): void { /* 函数实现 */ } foo(123); // OK,使用第一个定义 foo('aa'); // OK,使用第二个定义
类
通过 class 关键字来定义一个类, 使用 constructor 定义构造函数。
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创建实例
let p = new Person('John', 'Smith'); console.log(p.fullName());
或者,可以使用对象字面量创建实例:
class Point { x: number = 0 y: number = 0 } let p: Point = {x: 42, y: 42};
字段
字段是直接在类中声明的某种类型的变量。
类可以具有实例字段或者静态字段。
使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。
ArkTS要求所有字段在声明时或者构造函数中显式初始化.
class Person { static numberOfPersons = 0 constructor() { Person.numberOfPersons++; } } let me = new Person() let you = new Person() console.log(Person.numberOfPersons+'' )//2
getter和setter
setter和getter可用于提供对对象属性的受控访问。
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值会抛出错误
方法
方法属于类。类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。
!!!所谓的静态方法和静态字段,都属于这个类,而非实例。所以实例是无法调用的,要调用只能通过这个类来调用。!!!
构造函数
类声明可以包含构造函数,使用constructor声明,所谓构造函数就是用来初始化对象状态的。
如果未定义构造函数,则会自动创建具有空参数列表的默认构造函数
构造函数重载签名
可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的构造函数头,构造函数实现紧随其后。
class C { constructor(x: number) /* 第一个签名 */ constructor(x: string) /* 第二个签名 */ constructor(x: number | string) { /* 实现签名 */ } } let c1 = new C(123); // OK,使用第一个签名 let c2 = new C('abc'); // OK,使用第二个签名
继承
使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
- 子类继承父类后子类的实例就拥有了父类中的属性和方法,但不继承构造函数。
- 子类自己特殊逻辑放在子类中重写父类的逻辑
- super 可以调用父类上的方法和属性
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实现对接口的继承,但是包含implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。
interface DateInterface { now(): string; } class MyDate implements DateInterface { now(): string { // 在此实现 return 'now is now'; } }
父类访问
关键字super可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口。
我们刚刚说过继承来的子类无法继承构造函数,如果要用的话就可以通过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 -可在此处使用 /* 填充矩形 */ } }
方法重写
子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。
方法重载签名
通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后
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,使用第二个签名
可见性修饰符
类的方法和属性都可以使用可见性修饰符。
可见性修饰符包括:private、protected和public。默认可见性为public。
- public 自己、自己的子类 和其他类都可以访问 (默认值)
- protected 受保护的 自己和自己的子类能访问, 其他类不能访问
- 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'不可见
Protected
class Base { protected x: string = '' private y: string = '' } class Derived extends Base { foo() { this.x = 'a'; // OK,访问受保护成员 this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的 } }
对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。
class C { n: number = 0 s: string = '' } let c: C = {n: 42, s: 'foo'};
对象字面量只能在可以推导出该字面量类型的上下文中使用
Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值
let map: Record<string, number> = { 'John': 25, 'Mary': 21, } map['John']; // 25
类型K可以是字符串类型或数值类型,而V可以是任何类型.
interface PersonInfo { age: number salary: number } let map: Record<string, PersonInfo> = { 'John': { age: 25, salary: 10}, 'Mary': { age: 21, salary: 20} }
接口
接口声明引入新类型。接口是定义代码协定的常见方式。
任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。
所谓多态,就是一个类只能继承一个父类,但是可以继承多个接口从而实现多态。
接口使用interface关键字声明
interface Style { color: string // 属性 } interface AreaSize { calculateAreaSize(): number // 方法的声明 someMethod(): void; // 方法的声明 }
类继承接口使用implements
// 接口: 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; } }
接口继承
接口也可以使用extends 实现继承
interface Style { color: string } interface ExtendedStyle extends Style { width: number }
泛型类型和函数
泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。
即将数据类型也作为参数传递
class CustomStack<Element> {//这里定义了一个Element形参,作为类型 public push(e: Element):void {//内部的push方法接受的参数使用Element为类型 // ... } }
let s = new CustomStack<string>();//在实例化的时候,传入Element的实参string用来当做push方法的入参类型 s.push('hello');
泛型约束
泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。
interface Hashable { hash(): number } class MyHashMap<Key extends Hashable, Value> { public set(k: Key, v: Value) { let h = k.hash(); // ...其他代码... } }
泛型函数
使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
function last(x: number[]): number { return x[x.length - 1]; } last([1, 2, 3]); // 3
以上方法只能用于number数组,
如果需要为任何数组定义相同的函数,使用类型参数将该函数定义为泛型:
function last<T>(x: T[]): T { return x[x.length - 1]; }
现在,该函数可以与任何数组一起使用。
// 显式设置的类型实参 last<string>(['aa', 'bb']); last<number>([1, 2, 3]); // 隐式设置的类型实参 // 编译器根据调用参数的类型来确定类型实参 last([1, 2, 3]);
泛型默认值
泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。
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>();