前言
臭寶們,今天我們來學習ArkTS中最后的一些內容。
實現接口
包含implements子句的類必須實現列出的接口中定義的所有方法,但使用默認實現定義的方法除外。
interface DateInterface {now(): string;
}
class MyDate implements DateInterface {now(): string {// 在此實現return 'now';}
}
接口屬性
接口屬性可以是字段、getter、setter或getter和setter組合的形式。
interface Style {color: string;
}
接口繼承
接口可以繼承其他接口,如下面的示例所示:
interface Style {color: string;
}interface ExtendedStyle extends Style {width: number;
}
注意:繼承接口包含被繼承接口的所有屬性和方法,還可以添加自己的屬性和方法。
抽象類和接口
在上一節中,我們介紹了如何在ArkTS中使用抽象類。抽象類與接口都無法實例化。抽象類是類的抽象,抽象類用來捕捉子類的通用特性,接口是行為的抽象。在ArkTS中抽象類與接口的區別如下:
- 一個類只能繼承一個抽象類,而一個類可以實現一個或多個接口;
- 接口中不能含有靜態代碼塊以及靜態方法,而抽象類可以有靜態代碼塊和靜態方法;
- 抽象類里面可以有方法的實現,但是接口完全都是抽象的,不存在方法的實現;
- 抽象類可以有構造函數,而接口不能有構造函數。
泛型在接口和類中的應用
class CustomStack<Element> {public push(e: Element):void {// ...}
}
要使用類型CustomStack,必須為每個類型參數指定類型實參:
let s = new CustomStack<string>();
s.push('hello');
編譯器在使用泛型類型和函數時會確保類型安全。參見以下示例:
let s = new CustomStack<string>();
s.push(55); // 將會產生編譯時錯誤
泛型約束
泛型類型的類型參數可以被限制只能取某些特定的值。例如,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();// ...其他代碼...}
}
在上面的例子中,Key類型擴展了Hashable,Hashable接口的所有方法都可以為key調用。
泛型函數
function last(x: number[]): number {return x[x.length - 1];
}
last([1, 2, 3]); // 3
如果需要為任何數組定義相同的函數,使用類型參數將該函數定義為泛型:
function last<T>(x: T[]): T {return x[x.length - 1];
}
現在,該函數可以與任何數組一起使用。
在函數調用中,類型實參可以顯式或隱式設置:
// 顯式設置的類型實參
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]);// 隱式設置的類型實參
// 編譯器根據調用參數的類型來確定類型實參
last([1, 2, 3]);
last(['aa', 'bb']);
泛型默認值
泛型類型的類型參數可以設置默認值。這樣可以不指定實際的類型實參,而只使用泛型類型名稱。下面的示例展示了類和函數的這一點。
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>();
空安全
在ArkTS中,ArkTS中的所有類型都是不可為空的,在下面的示例中,所有行都會導致編譯時錯誤:
let x: number = null; // 編譯時錯誤
let y: string = null; // 編譯時錯誤
let z: number[] = null; // 編譯時錯誤
可以為空值的變量定義為聯合類型T | null。
let x: number | null = null;
x = 1; // ok
x = null; // ok
if (x != null) { /* do something */ }
非空斷言運算符(!)
后綴運算符!可用于斷言其操作數為非空。
應用于可空類型的值時,它的編譯時類型變為非空類型。例如,類型將從T | null更改為T:
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方法如果設置了昵稱,則返回昵稱;否則,返回空字符串:
class Person {// ...nick: string | null = null;getNick(): string {return this.nick ?? '';}
}
可選鏈
在訪問對象屬性時,如果該屬性是undefined或者null,可選鏈運算符會返回undefined。
class Person {nick: string | null = null;spouse?: PersonsetSpouse(spouse: Person): void {this.spouse = spouse;}getSpouseNick(): string | null | undefined {return this.spouse?.nick;}constructor(nick: string) {this.nick = nick;this.spouse = undefined;}
}
模塊
程序可劃分為多組編譯單元或模塊。
每個模塊都有其自己的作用域,即,在模塊中創建的任何聲明(變量、函數、類等)在該模塊之外都不可見,除非它們被顯式導出。
與此相對,從另一個模塊導出的變量、函數、類、接口等必須首先導入到模塊中。
導出
可以使用關鍵字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));
}
導入
導入聲明用于導入從其他模塊導出的實體,并在當前模塊中提供其綁定。導入聲明由兩部分組成:
- 導入路徑,用于指定導入的模塊;
- 導入綁定,用于定義導入的模塊中的可用實體集和使用形式(限定或不限定使用)。
假設模塊具有路徑“./utils”和導出實體“X”和“Y”。
導入綁定* as A表示綁定名稱“A”,通過A.name可訪問從導入路徑指定的模塊導出的所有實體:導入綁定可以有幾種形式。
import * as Utils from './utils';
Utils.X // 表示來自Utils的X
Utils.Y // 表示來自Utils的Y
導入綁定{ ident1, …, identN }表示將導出的實體與指定名稱綁定,該名稱可以用作簡單名稱:
import { X, Y } from './utils';
X // 表示來自utils的X
Y // 表示來自utils的Y
如果標識符列表定義了ident as alias,則實體ident將綁定在名稱alias下:
import { X as Z, Y } from './utils';
Z // 表示來自Utils的X
Y // 表示來自Utils的Y
X // 編譯時錯誤:'X'不可見
動態導入
應用開發的有些場景中,如果希望根據條件導入模塊或者按需導入模塊,可以使用動態導入代替靜態導入。
import()語法通常稱為動態導入(dynamic import),是一種類似函數的表達式,用來動態導入模塊。以這種方式調用,將返回一個promise。
如下例所示,import(modulePath)可以加載模塊并返回一個promise,該promise resolve為一個包含其所有導出的模塊對象。該表達式可以在代碼中的任意位置調用。
// 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)。
// say.ts
export function hi() {console.log('Hello');
}
export function bye() {console.log('Bye');
}
async function test() {let ns = await import('./say');let hi = ns.hi;let bye = ns.bye;hi();bye();
}
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的指向:
- 調用實例方法的對象
- 正在構造的對象
結尾
至此,我們已經學習了ArkTS的基礎語法。下一步,我們將學習ArkUI框架。臭寶們,沖鴨!