一、背景
HarmonyOS 應用的主要開發語言是 ArkTS,它由 TypeScript(簡稱TS)擴展而來,在繼承TypeScript語法的基礎上進行了一系列優化,使開發者能夠以更簡潔、更自然的方式開發應用。值得注意的是,TypeScript 本身也是由另一門語言 JavaScript 擴展而來。因此三者的關系如下圖所示
二、TypeScript
2.1、運行環境
2.1.1、線上Playground
TypeScript提供了一個線上的 Playground 供練習使用,地址為TypeScript: 演練場 - 一個用于 TypeScript 和 JavaScript 的在線編輯器。
2.1.2、本地運行環境?
2.1.2.1、采用的是VSCode 編輯器
2.1.2.2、前提條件
1、安裝插件:Code Runner:它提供了簡便的代碼執行功能,支持多種編程語言,使開發者能夠快速運行和調試代碼片段。
2、安裝Node.js并配置Path
環境變量
說明:我已經安裝了Node.js,便不重復安裝了
為了方便在終端執行Node.js相關的命令,我們需要將Node.js的安裝目錄加入到Path
環境變量下
步驟1:首先在DevEco Studio的設置界面查看Node.js的安裝目錄
步驟2:然后打開環境變量配置面板,按下?Win
+R?
,喚起運行窗口,之后運行命令?sysdm.cpl?
步驟3:點擊高級選項卡,并點擊環境變量
步驟4:在系統變量中選中?Path?
,并點擊編輯
步驟5:點擊新建,并填入Node.js的安裝目錄,完成后點擊確定
3、安裝ts-node
這是一個 TypeScript 的運行環境,它允許我們直接運行 TypeScript 代碼。ts-node的安裝和運行依賴于Node.js環境,已經安裝了Node.js
npm install -g ts-node
在終端中輸入node -v 和ts-node -v后出現各自版本,說明這兩個已經安裝成功了,如下:
注:完成后需要重新啟動VSCode,另其重新加載環境變量和相關依賴。
2.1.2.3、編寫程序并運行
首先在合適的位置創建一個工程目錄,例如C:\Users\......\hello-ts
,然后使用VSCode打開目錄,創建ts文件,并編寫Typescript代碼運行
問題:運行ts時出現以下錯誤,百度后發現是由于 ts-node 版本升級導致的兼容性問題
解決:需要在項目中安裝??@types/node?
npm install --save-dev @types/node
效果:?安裝??@types/node?后,再運行就能夠正常打印日志了
2.2、聲明
2.2.1、變量聲明
2.2.2、常量聲明
?let
用于聲明變量,而const
用于聲明常量,兩者的區別是變量在賦值后可以修改,而常量在賦值后便不能再修改。
const b:number = 200;
2.2.3、類型推斷?
如果一個變量或常量的聲明包含了初始值,TS 便可以根據初始值進行類型推斷,此時我們就可以不顯式指定其類型,例如
let c = 60;
console.log(typeof c); //number
2.3、常用數據類型?
2.3.1、number
number
表示數字,包括整數和浮點數,例如: 100
、-33
、2.5
、-3.9
let a :number = 100
let b :number = -33
let c :number = 2.5
let d :number = -3.9
2.3.2、string?
string
表示字符串,例如: 你好
、hello
let a:string = '你好'
let b:string = 'hello'
?2.3.3、boolean
boolean
表示布爾值,可選值為:true
、false
let isOpen:boolean = true
let isDone:boolean = false
?2.3.4、數組
數組類型定義由兩部分組成,元素類型[]
,例如number[]
表示數字數組,string[]
表示字符串數組,數組類型的變量可由數組字面量——[item1,item2,item3]
進行初始化。
let a: number[] = []
let b: string[] = ['你好', 'hello']
2.3.5、對象?
在TS中,對象(object)是一種一系列由屬性名稱和屬性值組成的數據結構,例如姓名:'張三', 年齡:10, 性別:'男'
。對象類型的聲明需要包含所有屬性的名稱及類型,例如{name: string, age: number, gender: string}
,對象類型的變量可以通過對象字面量——{name:'張三', age:10, gender:'男'}
進行初始化。
let person: {name:string, age:number, gender:string} = {name:'張三', age:10, gender:'男'};
2.4、函數?
2.4.1、函數聲明語法
聲明函數的基礎語法如下
2.4.2、參數
2.4.2.1、特殊語法
①可選參數
可選參數通過參數名后的?
進行標識,如以下案例中的gender?
參數。
function getPersonInfo(name: string, age: number, gender?: string): string {if (gender === undefined) {gender = '未知'}return `name:${name},age:${age},gender:${gender}`;
}let p1 = getPersonInfo('zhagnsan', 10, '男')
let p2 = getPersonInfo('lisi', 15);
console.log(p1);
console.log(p2);
注:調用函數時,未傳遞可選參數,則該參數的值為undefined
。
②默認參數
可在函數的參數列表為參數指定默認值,如以下案例中的gender: string='未知'
參數。
function getPersonInfo(name: string, age: number, gender: string='未知'): string {return `name:${name},age:${age},gender:${gender}`;
}let p1 = getPersonInfo('zhagnsan', 10, '男')
let p2 = getPersonInfo('lisi', 15);
console.log(p1);
console.log(p2);
2.4.2.2、 特殊類型
①聯合類型
一個函數可能用于處理不同類型的值,這種情況可以使用聯合類型,例如以下案例中的message: number | string
function printNumberOrString(message: number | string) {console.log(message)
}printNumberOrString('a')
printNumberOrString(1)
②任意類型
?若函數需要處理任意類型的值,則可以使用any
類型,例如以下案例中的message: any
function print(message:any) {console.log(message)
}print('a')
print(1)
print(true)
2.4.3、 返回值
2.4.3.1、特殊類型
若函數沒有返回值,則可以使用void
作為返回值類型,其含義為空。
function test(): void {console.log('hello');
}
2.4.3.2、類型推斷
函數的返回值類型可根據函數內容推斷出來,因此可以省略不寫。
function test() {console.log('hello');
}function sum(a: number, b: number) {return a + b;
}
2.4.4、函數聲明特殊語法
①匿名函數
匿名函數的語法結構簡潔,特別適用于簡單且僅需一次性使用的場景。
let numbers: number[] = [1, 2, 3, 4, 5]
numbers.forEach(function (number) {console.log(number);
})
注意:匿名函數能夠根據上下文推斷出參數類型,因此參數類型可以省略。
②箭頭函數
匿名函數的語法還可以進一步的簡化,只保留參數列表和函數體兩個核心部分,兩者用=>
符號連接。
let numbers: number[] = [1, 2, 3, 4, 5]
numbers.forEach((num) => { console.log(num) })
2.5、類
2.5.1、類介紹
類(class)是面向對象編程語言中的一個重要概念。
面向對象編程(Object-Oriented Programming,簡稱OOP)是一種編程范式,其核心理念在于將程序中的數據與操作數據的方法有機地組織成對象,從而使程序結構更加模塊化和易于理解。通過對象之間的協同合作,實現更為復雜的程序功能。
類(class)是對象的藍圖或模板,它定義了對象的屬性(數據)和行為(方法)。通過類可以創建多個具有相似結構和行為的對象。例如定義一個 Person
類,其對象可以有張三
、李四
等等。
2.5.2、語法說明
2.5.2.1、類的定義
class Person {id: number;name: string;age: number = 18;constructor(id: number, name: string) {this.id = id;this.name = name;}introduce(): string {return `hello,I am ${this.name},and I am ${this.age} years old`}
}
2.5.2.2、對象創建
①語法
創建對象的關鍵字為new
,具體語法如下
let person = new Person(1,'zhangsan');
②對象屬性的訪問
console.log(person.name); //讀person.name = 'lisi'; //寫console.log(person.name);
③對象方法的調用
對象創建后,便可通過對象調用類中聲明的方法,如下
let intro = person.introduce();
console.log(intro);
2.5.2.3、靜態成員
Typescript 中的類中可以包含靜態成員(靜態屬性和靜態方法),靜態成員隸屬于類本身,而不屬于某個對象實例。靜態成員通用用于定義一些常量,或者工具方法。
①聲明靜態成員
定義靜態成員需要使用static
關鍵字。
class Constants{static count:number=1;
}class Utils{static toLowerCase(str:string){return str.toLowerCase();}
}console.log(Constants.count);
console.log(Utils.toLowerCase('Hello World'));
②使用靜態成員
靜態成員無需通過對象實例訪問,直接通過類本身訪問即可。
console.log(Constants.count);
console.log(Utils.toLowerCase('Hello World'));
2.5.3、繼承
繼承是面向對象編程中的重要機制,允許一個類(子類或派生類)繼承另一個類(父類或基類)的屬性和方法。子類可以直接使用父類的特性,并根據需要添加新的特性或覆蓋現有的特性。這種機制賦予面向對象程序良好的擴展性。
class Student extends Person {classNumber: string;constructor(id: number, name: string, classNumber: string) {super(id, name);this.classNumber = classNumber;}introduce(): string {return super.introduce()+`, and I am a student`;}
}let student = new Student(1,'xiaoming','三年二班');
console.log(student.introduce());
注意:
- 類的繼承需要使用關鍵字
extends
- 子類構造器中需使用
super()
調用父類構造器對繼承自父類的屬性進行初始化。- 在子類中可以使用
this
關鍵字訪問繼承自父類的屬性和方法。- 在子類中可以使用
super
關鍵字訪問父類定義的方法。
2.5.4、訪問修飾符
訪問修飾符(Access Modifiers)用于控制類成員(屬性、方法等)的可訪問性。TypeScript提供了三種訪問修飾符,分別是private、protected和public。
class Person {private id: number;protected name: string;public age: number;constructor(id: number, name: string, age: number) {this.id = id;this.name = name;this.age = age;}
}class Student extends Person {}
說明:
- private 修飾的屬性或方法是私有的,只能在聲明它的類中的被訪問。
- protected 修飾的屬性或方法是受保護的,只能在聲明它的類和其子類中被訪問。
- public 修飾的屬性或方法是公有的,可以在任何地方被訪問到,默認所有的屬性和方法都是 public 的。
2.6、接口
2.6.1、接口介紹
接口(interface)是面向對象編程中的另一個重要概念。接口通常會作為一種契約或規范讓類(class)去遵守,確保類實現某些特定的行為或功能。
2.6.2、語法說明
①接口定義
接口使用interface
關鍵字定義,通常情況下,接口中只會包含屬性和方法的聲明,而不包含具體的實現細節,具體的細節由其實現類完成。
interface Person {id: number;name: string;age: number;introduce(): void;
}
②接口實現
接口的實現需要用到implements
關鍵字,實現類中,需要包含接口屬性的賦值邏輯,以及接口方法的實現邏輯。
class Student implements Person {id: number;name: string;age: number;constructor(id: number, name: string, age: number) {this.id = id;this.name = name;this.age = age;}introduce(): void {console.log('Hello,I am a student');}
}
2.6.3、多態
多態是面相對象編程中的一個重要概念,它可以使同一類型的對象具有不同的行為。下面我們通過一個具體的案例來體會多態這一概念
首先,再創建一個Person
接口的實現類Teacher
,如下
class Teacher implements Person {id: number;name: string;age: number;constructor(id: number, name: string, age: number) {this.id = id;this.name = name;this.age = age;}introduce(): void {console.log('Hello,I am a teacher');}
}
然后分別創建一個Student
對象和一個Teacher
對象,注意兩個對象的類型均可以設置Person
,如下
let p1: Person = new Student(1, 'zhangsan', 17);
let p2: Person = new Teacher(2, 'lisi', 35);
最后分別調用p1
和p2
的introduce()
方法,你會發現,同樣是Person
類型的兩個對象,調用同一個introduce()
方法時,表現出了不同的行為,這就是多態。
p1.introduce();//Hello,I am a student
p2.introduce();//Hello,I am a teacher
2.6.4、接口的作用
在傳統的面向對象編程的場景中,接口主要用于設計和組織代碼,使代碼更加容易擴展和維護。
假如現在需要實現一個訂單支付系統,按照面向對象編程的習慣,首先需要定義一個訂單類(Order),如下
class Order {totalAmount: number;constructor(totalAmount: number) {this.totalAmount = totalAmount;}pay() {console.log(`AliPay:${this.totalAmount}`);}
}
很容易預想到,這個系統將來可能需要支持其他的支付方式,為了方便代碼支持新的支付方式,我們可以對代碼進行如下改造。
首先定義一個支付策略的接口,接口中聲明一個pay
方法,用來規范實現類必須實現支付邏輯。
interface PaymentStrategy {pay(amount: number): void;
}
然后在訂單類中增加一個PaymentStrategy
的屬性,并且在訂單類中的pay
方法中調用PaymentStrategy
的pay
方法,如下
class Order {totalAmount: number;paymentStrategy: PaymentStrategy;constructor(totalAmount: number, paymentStrategy: PaymentStrategy) {this.totalAmount = totalAmount;this.paymentStrategy = paymentStrategy;}pay() {this.paymentStrategy.pay(this.totalAmount);}
}
這樣改造完之后,就可以很容易的在不改變現有代碼的情況下,支持新的支付方式了。
比如現在需要支持AliPay
,那我們就可以創建AliPay
這個類(class)并實現(implement)PaymentStrategy
這個接口,如下
class AliPay implements PaymentStrategy {pay(amount: number): void {console.log(`AliPay:${amount}`);}
}
這樣一來,之后創建的訂單就可以使用AliPay
這個支付方式了。
let order = new Order(1000,new AliPay());
order.pay();
2.6.5、TS中接口的特殊性
TypeScript 中的接口是一個非常靈活的概念,除了用作類的規范之外,也常用于直接描述對象的類型,例如,現有一個變量的定義如下
let person: {name:string, age:number, gender:string} = {name:'張三', age:10, gender:'男'};
可以看到變量的值為一個一般對象,變量的類型為{name:string, age:number, gender:string}
,此時就可以聲明一個接口來描述該對象的類型,如下
interface Person {name: string;age: number;gender: string;
}let person: Person = {name:'張三', age:10, gender:'男'};
2.7、枚舉
2.7.1、枚舉介紹
枚舉(Enumeration)是編程語言中常見的一種數據類型,其主要功能是定義一組有限的選項,例如,方向(上、下、左、右)或季節(春、夏、秋、冬)等概念都可以使用枚舉類型定義。
2.7.2、語法說明
①枚舉定義
枚舉的定義需使用enum
關鍵字,如下
enum Season {SPRING,SUMMER,AUTUMN,WINTER
}
②枚舉使用
枚舉的使用記住兩個原則即可
- 枚舉值的訪問
像訪問對象屬性一樣訪問枚舉值,例如
Season.SPRING
- 枚舉值的類型
枚舉值的類型為
enum
的名稱,例如Season.SPRING
和Season.SUMMER
等值的類型都是Season
let spring:Season = Season.SPRING;
③使用場景
現需要編寫一個函數move
,其功能是根據輸入的方向(上、下、左、右)進行移動,此時就可以先使用枚舉定義好所有可能的輸入選項,如下
enum Direction {UP,BOTTOM,LEFT,RIGHT
}
move
函數的實現如下
function move(direction: Direction) {if(direction===Direction.UP){console.log('向上移動');}else if(direction===Direction.BOTTOM){console.log('向下移動');}else if(direction===Direction.LEFT){console.log('向左移動');}else{console.log('向右移動');}
}move(Direction.UP);
2.7.3、賦值
在TypeScript 中,枚舉實際上是一個對象,而每個枚舉值都是該對象的一個屬性,并且每個屬性都有具體的值,屬性值只支持兩種類型——數字或字符串。
默認情況下,每個屬性的值都是數字,并且從 0
開始遞增,例如上述案例中的Direction
枚舉中,Direction.UP
的值為0
,Direction.BOTTOM
的值為1
,依次類推,具體如下
console.log(Direction.UP) //0
console.log(Direction.BOTTOM) //1
console.log(Direction.LEFT) //2
console.log(Direction.RIGHT) //3
除了使用默認的數字作為屬性的值,我們還能手動為每個屬性賦值,例如
enum Direction {UP = 1,BOTTOM = 2,LEFT = 3,RIGHT = 4
}console.log(Direction.UP) //1
console.log(Direction.BOTTOM) //2
console.log(Direction.LEFT) //3
console.log(Direction.RIGHT) //4
再例如
enum Direction {UP = 'up',BOTTOM = 'bottom',LEFT = 'left',RIGHT = 'right'
}console.log(Direction.UP) //up
console.log(Direction.BOTTOM) //bottom
console.log(Direction.LEFT) //left
console.log(Direction.RIGHT) //right
通過為枚舉屬性賦值,可以賦予枚舉屬性一些更有意義的信息,例如以下枚舉
enum Color {Red = 0xFF0000,Green = 0x00FF00,Blue = 0x0000FF
}enum FontSize {Small = 12,Medium = 16,Large = 20,ExtraLarge = 24
}
2.8、模塊化
2.8.1、模塊化介紹
模塊化是指將復雜的程序拆解為多個獨立的文件單元,每個文件被稱為一個模塊。在 TypeScript 中,默認情況下,每個模塊都擁有自己的作用域,這意味著在一個模塊中聲明的任何內容(如變量、函數、類等)在該模塊外部是不可見的。為了在一個模塊中使用其他模塊的內容,必須對這些內容進行導入、導出。
2.8.2、語法說明
①導出
導出須使用export
關鍵字,語法如下
export function hello() {console.log('hello module A');
}export const str = 'hello world';const num = 1;
②導入
導入須使用import
關鍵字,語法如下
import { hello, str } from './moduleA';hello();
console.log(str);
2.8.3、避免命名沖突
若多個模塊中具有命名相同的變量、函數等內容,將這些內容導入到同一模塊下就會出現命名沖突。例如,在上述案例的基礎上,又增加了一個 moduleC,內容如下
export function hello() {console.log('hello module C');
}export const str = 'module C';
moduleB 同時引入 moduleA 和 moduleC 的內容,如下,顯然就會出命名沖突
import { hello, str } from "./moduleA";
import { hello, str } from "./moduleC";hello() //?
console.log(str); //?
①導入重命名
import { hello as helloFromA, str as strFromA } from "./moduleA";
import { hello as helloFromC, str as strFromC } from "./moduleC";helloFromA();
console.log(strFromA);helloFromC();
console.log(strFromC);
②創建模塊對象
上述導入重命名的方式能夠很好的解決命名沖突的問題,但是當沖突內容較多時,這種寫法會比較冗長。除了導入重命名外,還可以將某個模塊的內容統一導入到一個模塊對象上,這樣就能簡潔有效的解決命名沖突的問題了,具體語法如下
import * as A from "./moduleA";
import * as C from "./moduleC";A.hello();
console.log(A.str);C.hello();
console.log(C.str);
2.8.4、默認導入導出
①默認導出
默認導出允許一個模塊指定一個(最多一個)默認的導出項,語法如下
export default function hello(){console.log('moduleA');
}
②默認導入
由于每個模塊最多有一個默認導出,因此默認導入無需關注導入項的原始名稱,并且無需使用{}
。
import helloFromA from "./moduleA";
由于默認導入時無需關注導入項的名稱,所以默認導出支持匿名內容,比如匿名函數,語法如下?
export default function () {console.log('moduleB');
}
最后:👏👏😊😊😊👍👍?