ArkTS介绍
基本概念
JS
:属于前端浏览器语言,弱类型语言,灵活,功能强大。
TS
:TS是微软设计出来的一门编程语言,是JS这门语言的超集,涵盖了JS
核心的es5/6
的内容,增加静态检擦类型推断等等,可以让开发过程中更加严谨,减少错误。
ArkTS
:鸿蒙官方主推的开发语言,高级编程语言。在TS
基础上进行了扩展,推出了全新声明式的开发规范。相当于基于TS
开发规则,有进行封装,将核心的内容封装起来次啊用声明式开发模式。
ArkTS
编程规范会比TS
更加严格,完全按照强类型语言规则要求。我们创建的文件以ets
结尾的文件,这个文件是一个ArkTS
文件。
ArkTS
包含:
- 装饰器:
ArkTS
将装饰器进行了封装,提供了各种装饰器来强化我们的组件,有类的装饰器,属性的装饰器,方法装饰器等,实现不同的业务功能。struct
来定义一个组件,这个组件内部开发模式严格采用面向对象的开发规则。类、属性、行为等等概念,this
的使用也是必须的。- 也可以在
ArkTS
中自定义组件,也可以使用官方提供系统组件(ArkUI)
提供的。- 还可以给组件绑定事件,还提供了完整的事件机制。
运行原理
我们写的ArkTS
代码运行流程,代码写完之后,系统是如何将代码加载出来并运行这个地方ArkComPiler
(方舟编译器)。
JS
:代码在浏览器运行必须依赖JS引擎(V8引擎)
arkts
:运行需要用到华为自己研发的方舟编译器。
ArkCompiler
是华为自己研发的统一编程平台,包含编译器,工具链,运行时等相关的部件。支持多种高级语言和多芯片平台的编译和运行。你写的代码可以通过方舟编译器将代码运行到手机,平板,车机,智慧屏,手表等等。
对比图:
普通JS代码运行:编写应用->前端插件对代码压缩混淆babel
转化版本->在浏览器运行流程。
优点:开发方便,设计代码也没有过多的约束。
缺点:开发不够规范,运行过程中可能出现一堆错误,慢慢的回到源码找到错误修改。
方舟编译器运行:基于deveco Studio
开发代码的时候,已经进行代码解析、编译。排查错误。对于开发者而言,需要在开发过程中将问题排查到减少上线不必要报错,针对不同平台设备,进行代码编译,让你写一套代码多端适配,核心设计思想1+8+n
,鸿蒙这个系统设计出来就是为了万物互联,让各种不同的设备都可以搭载的系统。
ArkUI入门
arkUI
实际上是方舟UI
,提供了各种丰富的组件,让开发者在开发过程中直接使用布局。将每个组件的功能、属性全都封装好了,声明式调用就可以了。
Row组件:
这个row
组件常用于布局,可以理解他是一个div
容器。Row
组件可以让子元素在水平方向进行排列,排列不下默认隐藏起来。
import { Header } from "../view/Header"
@Entry
@Component
struct Index {
@State message: string = "aaaa";
build() {
Column(){
Button("测试").onClick((event: ClickEvent) => {
})
// 布局容器组件:运行子元素再水平方向排列
Column(){
// 相当于以前的span组件
Text("这是row组件")
Text("harmonyOS")
Row(){
Text("test1")
.fontSize(20)
.fontColor("#F9ff")
.fontWeight(500)
Text("test2")
}
Text("harmonyOS")
Text("harmonyOS")
Text("harmonyOS")
Text("harmonyOS")
Text("harmonyOS")
Text("harmonyOS")
Text("harmonyOS")
Text("harmonyOS")
.width(30)
.height(30)
.backgroundColor("#f8ff")
}
}
}
}
效果:
Column组件
这是一个容器组件,一般用于页面布局,允许子元素再垂直方向上面进行排列,如果垂直方向放不下,超过部分默认也是隐藏起来。
import { Header } from "../view/Header"
@Entry
@Component
struct Index {
@State message: string = "aaaa";
build() {
Column(){
Button("测试").onClick((event: ClickEvent) => {
})
// 布局容器组件:运行子元素再水平方向排列
Column(){
// 相当于以前的span组件
Text("这是row组件")
Text("harmonyOS")
}
}
}
}
效果:
Text组件
代表问而不能组件,可以将文本内容写到这个组件中,并设置对应的属性来调整样式。
// 相当于以前的span组件
Text("这是row组件")
Text("harmonyOS")
.width(30)
.height(30)
.backgroundColor("#f8ff")
如果有CSS基础,基本上这个代码无需查询文档,直接将以前的CSS的属性,变成arkui的属性就可以了。
指定组件添加属性
Column({space:20}){
// 相当于以前的span组件
Text("这是row组件")
Text("harmonyOS")
.width(30)
.height(30)
.backgroundColor("#f8ff")
}
{space:20}这个代码就是代表属性,允许控制当前组建的一些基本设置。
鸿蒙国际化配置
鸿蒙开发过程中默认已经配置好了国际化内容,需要开发者自己将对应布局标签文字放在指定的文件夹下面,实现国际化,根据操作系统语言自动切换中英文,也可以手动切换中英文,其中提供了多套语言包,在编写代码过程中,不要将文字写死,将文本放在指定的文件夹中的文件来保存,后续遇到需要进行国际化切换的时候可以采用不同的语言包,鸿蒙开发中默认实现中英文开发配置。
在resources/base/element/string.json
文件中写入
{
"string": [
{
"name": "title",
"value": "App Title"
},
{
"name": "notice_text",
"value": "请输入用户名"
}
]
}
在resources/en_US/element/string.json
文件中写入
{
"string": [
{
"name": "title",
"value": "App Title"
},
{
"name": "notice_text",
"value": "Please enter userName"
}
]
}
在resources/zh_CN/element/string.json
文件中写入
{
"string": [
{
"name": "title",
"value": "应用程序的标题"
},
{
"name": "notice_text",
"value": "请输入用户名"
}
]
}
里面配置的键值对页面需要使用的时候,如下:
Text($r("app.string.title"))
使用代码
@Entry
@Component
struct LanguogePage{
@State message:string = 'hellow World';
build() {
Column(){
Text($r("app.string.title"))
.fontColor($r("app.color.bg_color"))
.fontSize($r("app.float.full_width"))
TextInput({placeholder:$r("app.string.notice_text")})
}.height("100%")
.width("100%")
}
}
效果:
ArkTs基础语法
基本知识
ArkTS是一种为构建高翔能应用二设计的编程语言。ArkTs在集成TypeScript语法的基础上进行了优化,以提供更高的性能和开发效率。
变量声明
let a:string = "WuYong"
常量声明
const b:string = 'Wuyong'
类型
-
number,任何整数浮点数都可被赋值给此类型的变量
let a1:number = 1.23; let a2 = 1.2345; let ae = .66; let a4 = 1e5;
-
boolean由true和false两个逻辑值组成
let isEdit:boolean = false
-
string代表字符序列
let s1:string = "hello,wuyong!"
-
void用于指定函数没有返回值
class Class<T>{ //... } let instance:Class<void>
-
Object所有引用类型的基类型
interface Person{ name:string } let person: Person = { name:"WuYong" } console.log(person.name);
-
array数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。
let names:string[] = ["WuYong","WuQi","WuMan","WuLun","ZhangYongfang"]
-
enum枚举类型,是预先定义的一组命名值得值类型,其中命名值又称为枚举常量,在ArkTS开发过程中,官方提供很多属性,属性值都已经采用枚举的类型进行封装了,enum类型又称枚举类型,是预先定义的一组命名值得值类型,其中命名值又称为枚举常量,使用枚举常量时必须以枚举类型名称为前缀。枚举类型是TS中提供得一种数据表达方式。
interface Order{ orderId:string orderType:string orderState:number } const order:Order = { orderId:"xxxx", orderType:"普通订单", orderState:1 // 1代表支付成功,0代表支付失败,2代表支付超时 } // 枚举得基本结构,当你得结果是固定的,并且是有限个数得,就可以用枚举来表示。 enum OrderState{ payFail = 0, paySuccess = 1, payTimeOut = 2 } if(order.orderState === OrderState.paySuccess){ // 跳转到xx页面 }else if(order.orderState === OrderState.payTimeOut){ // 提示用户支付超时 }else if(order.orderState === OrderState.payFail){ // 支付失败,重新支付 }
-
union类型,即联合类型,是由多个类型组合成的引用类型,联合类型包含了变量可能的所有类型
class Cat{ //... } class Dog{ //... } class Frog{ //... } type Animal = Cat | Dog | Frog | number let animal:Animal = new Cat(); // 可以将类型为联合类型的变量赋值为任何组成类型的有效值 animal = new Frog() animal = 42
-
Aliases
类型:Aliases
类型为匿名类型(数组,函数,对象字面量或联合类型)提供名称,或为已有类型提供替代名称type 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
。
复合赋值运算符列举如下:+=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、|=、^=
。 -
比较运算符
==、!=、>、>=、<、<=
-
算术运算符
+,-,*,/,++,--,%
-
位运算符
a&b
:按位与,如果两个操作数的对应位都为1
,则将这个位设置为1
,否则设置为0
。
a|b
:按位或,如果两个操作数的相应位中至少有一个为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
语句
if语句用于需要根据逻辑条件执行不同语句的场景。当逻辑条件为真时,执行对应的一组语句,否则执行另一组语句(如果有的话)。else
部分也可能包含if
语句。
if
语句如下所示:
if (condition1) {
// 语句1
} else if (condition2) {
// 语句2
} else {
// else语句
}
// 条件表达式可以是任何类型。但是对于boolean以外的类型,会进行隐式类型转换:
let s1 = 'Hello';
if (s1) {
console.log(s1); // 打印“Hello”
}
let s2 = 'World';
if (s2.length != 0) {
console.log(s2); // 打印“World”
}
-
Switch语句
使用switch
语句来执行与switch
表达式值匹配的代码块。
switch
语句如下所示:switch (expression) { case label1: // 如果label1匹配,则执行 // ... // 语句1 // ... break; // 可省略 case label2: case label3: // 如果label2或label3匹配,则执行 // ... // 语句23 // ... break; // 可省略 default: // 默认语句 }
如果
switch
表达式的值等于某个label
的值,则执行相应的语句,如果没有任何一个label值与表达式值相匹配,并且switch具有default
子句,那么程序会执行default
子句对应的代码块,break
语句(可选的)允许跳出switch语句并继续执行switch
语句之后的语句,如果没有break
语句,则执行switch
中的下一个label
对应的代码块。 -
三目运算符
条件表达式由第一个表达式的布尔值来决定返回其它两个表达式中的哪一个。
示例如下:
typescript condition ? expression1 : expression2
如果condition
的为真值(转换后为true
的值),则使用expression1
作为该表达式的结果;否则,使用expression2
。
示例:let isValid = Math.random() > 0.5 ? true : false; let message = isValid ? 'Valid' : 'Failed';
循环语句
-
For
语句
for
语句会被重复执行,直到循环退出语句值为false
。
for
语句如下所示:for ([init]; [condition]; [update]) { statements }
for
语句的执行流程如下:1、 执行init表达式(如有)。此表达式通常初始化一个或多个循环计数器。
2、 计算condition
。如果它为真值(转换后为true
的值),则执行循环主体的语句。如果它为假值(转换后为false
的值),则for循环终止。
3、 执行循环主体的语句。
4、 如果有update
表达式,则执行该表达式。
5、 回到步骤2。
示例:let sum = 0; for (let i = 0; i < 10; i += 2) { sum += i; }
-
For-of
语句
使用for-of
语句可遍历数组或字符串。示例如下:for (forVar of expression) { statements }
示例:
for (let ch of 'a string object') { /* process ch */ }
-
While
语句
只要condition
为真值(转换后为true
的值),while
语句就会执行statements
语句。示例如下:while (condition) { statements }
示例:
let n = 0; let x = 0; while (n < 3) { n++; x += n; }
-
Do-while
语句
如果condition
的值为真值(转换后为true
的值),那么statements
语句会重复执行。示例如下:do { statements } while (condition)
示例:
let i = 0; do { i += 1; } while (i < 10)
-
Break
语句
使用break
语句可以终止循环语句或switch
。
示例:let x = 0; while (true) { x++; if (x > 5) { break; } }
如果
break
语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
示例:let x = 1 label: while (true) { switch (x) { case 1: // statements break label // 中断while语句 } }
-
Continue
语句
continue
语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。
示例:let sum = 0; for (let x = 0; x < 100; x++) { if (x % 2 == 0) { continue } sum += x; }
异常处理
-
Throw
和Try
语句throw
语句用于抛出异常或错误:throw new Error('this error')
try
语句用于捕获和处理异常或错误:try { // 可能发生异常的语句块 } catch (e) { // 异常处理 }
下面的示例中
throw和try
语句用于处理除数为0
的错误:class ZeroDivisor extends Error {} function divide (a: number, b: number): number{ if (b == 0) throw new ZeroDivisor(); return a / b; } function process (a: number, b: number) { try { let res = divide(a, b); console.log('result: ' + res); } catch (x) { console.log('some error'); } }
支持
finally
语句:function processData(s: string) { let error: Error | null = null; try { console.log('Data processed: ' + s); // ... // 可能发生异常的语句 // ... } catch (e) { error = e as Error; // ... // 异常处理 // ... } finally { if (error != null) { console.log(`Error caught: input='${s}', message='${error.message}'`); } } }
函数
- 函数声明:包含其名称,参数列表,返回类型和函数体
function demoFun(defaultName:string):string{ return defaultName === 'WuYong'?defaultName:"WuMan" }
- 可选参数:格式为
name?:type
可选参数的另一种形式为设置的参数默认值。如果在函数调用中这个参数被省略了,则会使用此参数的默认值作为实参。function getNameFun(defaultName?:string):string{ return defaultName === 'WuYong'?defaultName:"WuMan" }
function getNameFun(defaultName:string = "WuMan"):string{ return defaultName; } getNameFun('WuYong') // 返回WuYong getNameFun() // 返回WuMan
Rest
参数
函数的最后一个参数可以是rest
参数。使用rest
参数时,允许函数或方法接受任意数量的实参。
function getNameFun(...names:string[]):string{
let result : string = "" // 返回结果
for (let i = 0; i < names.length; i++) {
result += names[i]+" "
}
return result
}
getNameFun('WuYong','WuMan') // 返回 WuYong WuMan
-
返回类型
如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型// 显式指定返回类型 function getNameFun(): string { return 'foo'; } // 推断返回类型为string function getNameFun() { return 'goo'; }
-
函数的作用域
函数中定义的变量和其他实例仅可以在函数内部访问,不能从外部访问。 如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。 -
箭头函数
函数可以定义箭头函数,箭头函数的返回类型可以省略,省略时,返回类型通过函数体推断。
表达式可以指定省略为箭头函数,使表达更简短。// 写法一 let sum = (x:number,y:number):number=>{ return x+y; } // 写法二、省略指定为箭头函数,表达更简短 let sum1 = (x:number,y:number) = >{ return x+y; } // 写法三、 let sum2 = (x:number,y:number)=> x+y
-
闭包:闭包是由函数及声明该函数的环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。
// z是执行f时创建的g箭头函数实例的引用。g的实例维持了对它的环境的引用(变量count存在其中)。因 此,当z被调用时,变量count仍可用。 function f(): () => number { let count = 0; let g = (): number => { count++; return count; }; return g; } let z = f(); z(); // 返回:1 z(); // 返回:2
-
函数重载
可通过编写重载,指定函数的不同调用方式。具体做法是为同一个函数写入同名但签名不同的函数头,函数实现紧随其后。// 重载 function foo(x: number): void; /* 第一个函数定义 */ function foo(x: string): void; /* 第二个函数定义 */ function foo(x: number | string): void { /* 函数实现 */ } foo(123); // OK,使用第一个定义 foo('aa'); // OK,使用第二个定义
类
对象定义
对象:客观存在的物体就是对象,是一个可以存储多个数据的容器。用于描述一个物体的特征和行为。包含{属性名:属性值}
类定义
类:类就是一种类型,是一种对象的抽象,对象是具体的实物,实体,类的具体化。
定义格式:
clss 类名{
字段声明
构造方法
普通方法
静态成员
}
对象的初始化
/*
* 公司有很多员工,员工有不同的种类,程序员,项目经理
* 员工都有共同的属性和行为
* 属性
* 姓名,性别,工作,将建
* 行为
* 方法,函数
* */
class Employee{
name:string = ''
sex:string = ''
sal:number = 0
comm:number = 0
// 构造方法:只能写一个,不能由多个构造方法
constructor(name: string, sex: string, sal: number, comm: number) {
this.name = name
this.sex = sex
this.sal = sal
this.comm = comm
}
show(){
console.log("ken",this.name,this.sex,this.sal,this.comm)
}
getIncome(){
return this.sal+this.sal
}
}
对象的创建:new
通过 new 关键字调用类的构造方法来创建类的实例。
new 类名(参数)
例:
import { Employee } from "../common/employee"
@Entry
@Component
struct Index {
build() {
Column() {
Button("对象测试1").onClick(()=>{
// emp就是一个对象,可以使用.去访问对象中的属性喝行为
let emp = new Employee("wyk",'男',10000,0)
emp.show()
console.log("wyk","收入",emp.getIncome())
})
}
.height("100%")
.width("100%")
}
}
更复杂的:
class BankAccount {
private _balance: number; // 私有属性
// 构造方法
constructor(initialBalance: number) {
if (initialBalance < 0) {
throw new Error("Initial balance cannot be negative");
}
this._balance = initialBalance;
}
// Getter:获取余额
get balance(): number {
return this._balance;
}
// Setter:设置余额
set balance(newBalance: number) {
if (newBalance < 0) {
throw new Error("Balance cannot be negative");
}
this._balance = newBalance;
}
// 方法:存款
deposit(amount: number): void {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this._balance += amount;
}
// 方法:取款
withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}
if (amount > this._balance) {
throw new Error("Insufficient balance");
}
this._balance -= amount;
}
}
// 创建对象
const account = new BankAccount(1000);
// 使用 getter 获取余额
console.log(account.balance); // 输出:1000
// 存款
account.deposit(500);
console.log(account.balance); // 输出:1500
// 取款
account.withdraw(300);
console.log(account.balance); // 输出:1200
// 使用 setter 修改余额
account.balance = 2000;
console.log(account.balance); // 输出:2000
// account.balance = -500; // 抛出错误:Balance cannot be negative
修饰符
修饰符:arkTs
中,类的修饰符主要包括public
,protected
和private
,这些修饰符用于控制类成员的访问权限,默认情况下,类属性的可访问权限为public
,意味着他们可以在类的任何地方被访问,private
修饰符可以将属性或方法限制在定义他们的类内部访问,而protected
修饰符允许他们在同一个包内的类中被访问,但不允许从包外部方位。
public
:允许在任何地方访问类成员。
class MyClass {
public name: string;
constructor(name: string) {
this.name = name;
}
public greet() {
console.log(`Hello, ${this.name}`);
}
}
const obj = new MyClass("Alice");
console.log(obj.name); // 可以直接访问
obj.greet(); // 可以调用
protected
:允许在同一包内的类访问,但不允许从包外部访问。
class Parent {
protected id: number;
constructor(id: number) {
this.id = id;
}
}
class Child extends Parent {
public getId(): number {
return this.id; // 子类可以访问
}
}
const child = new Child(123);
console.log(child.getId());
// console.log(child.id); // 错误:'id' 是受保护的
private
:仅能在定义它们的类内部访问。
class MyClass {
private age: number;
constructor(age: number) {
this.age = age;
}
public getAge(): number {
return this.age; // 内部可以访问
}
}
const obj = new MyClass(25);
console.log(obj.getAge()); // 通过公共方法访问
// console.log(obj.age); // 错误:'age' 是私有的
构造方法
ArkTs
中,get
和set
是用于定义类的访问器属性的关键字,它们允许你通过方法的方式来控制对象的成员和读取和写入操作,从而实现更灵活的数据封装和验证。
set
:用于定义一个获取属性值的方法,当方位该属性的时候,会调用get
方法。
get
:用于定义一个设置属性值的方法,当该属性赋值时,会调用set方法。
通过get
和set
可以衍生数据的有效性,控制对私有或受保护属性的访问,实现计算属性。
示例1:基本使用
class Person {
private _name: string;
constructor(name: string) {
this._name = name; // 初始化私有属性
}
// Getter:获取 _name 的值
get name(): string {
return this._name;
}
// Setter:设置 _name 的值
set name(newName: string) {
if (newName.length === 0) {
throw new Error("Name cannot be empty");
}
this._name = newName;
}
}
const person = new Person("Alice");
// 使用 getter 获取值
console.log(person.name); // 输出:Alice
// 使用 setter 设置值
person.name = "Bob";
console.log(person.name); // 输出:Bob
// person.name = ""; // 抛出错误:Name cannot be empty
示例2:与构造方法结合,可以在构造方法中初始化私有属性,并通过get
和set
提供安全的访问方式。
class Rectangle {
private _width: number;
private _height: number;
constructor(width: number, height: number) {
this._width = width;
this._height = height;
}
// Getter 和 Setter for width
get width(): number {
return this._width;
}
set width(value: number) {
if (value <= 0) {
throw new Error("Width must be positive");
}
this._width = value;
}
// Getter 和 Setter for height
get height(): number {
return this._height;
}
set height(value: number) {
if (value <= 0) {
throw new Error("Height must be positive");
}
this._height = value;
}
// 计算面积
get area(): number {
return this._width * this._height;
}
}
const rect = new Rectangle(10, 5);
console.log(rect.area); // 输出:50
rect.width = 20; // 修改宽度
console.log(rect.area); // 输出:100
// rect.width = -5; // 抛出错误:Width must be positive
注意: 通常私有属性以_
开头(如:_name)
,以避免与get
和set
方法冲突,如果直接使用name
没有下划线,则会导致递归调用。
class Person {
private name: string;
get name(): string {
return this.name; // 无限递归!
}
set name(value: string) {
this.name = value; // 无限递归!
}
}
如果需要只读属性,可以只定义get
,而不定义set
。
class Circle {
private _radius: number;
constructor(radius: number) {
this._radius = radius;
}
get radius(): number {
return this._radius;
}
get area(): number {
return Math.PI * this._radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.radius); // 输出:5
console.log(circle.area); // 输出:78.5398...
// circle.radius = 10; // 错误:无法设置只读属性
在set
方法中,确保对输入值进行类型检查或验证,以保证数据的完整性。
继承
ArkTs
中,继承是面向对象编程的一个重要特性。它允许你创建一个新的子类,从现有父类哪里集成属性和方法,通过集成,你可以重用代码并建立类之间的层次结构。
使用 extends
关键字来定义一个类如何继承另一个类。
class 子类 extends 父类{
}
通过继承,子类可以拥有父类的非私有的属性和方法,通过继承,子类还可以重写父类的方法。
基本语法:
class Parent {
constructor(protected name: string) {
// 初始化逻辑
}
public greet(): void {
console.log(`Hello, my name is ${this.name}`);
}
}
class Child extends Parent {
constructor(name: string, private age: number) {
super(name); // 调用父类构造函数
}
public introduce(): void {
console.log(`My name is ${this.name} and I am ${this.age} years old.`);
}
}
上面例子中,Child
类继承了 Parent
类,并且可以访问 Parent
类中的 greet
方法。同时,Child
类新增了一个 introduce
方法。
调用父类的方法和属性:在子类的构造函数中,使用super()
来调用父类的构造函数。你也可以使用super
来调用父类的方法。
class Animal {
constructor(protected name: string) {}
move(distanceInMeters: number = 0): void {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark(): void {
console.log('Woof! Woof!');
}
move(distanceInMeters = 5): void {
console.log("Dog barks before moving...");
super.move(distanceInMeters); // 调用父类的 move 方法
}
}
const dog = new Dog("Rex");
dog.bark(); // 输出:Woof! Woof!
dog.move(); // 输出:Dog barks before moving... Rex moved 5m.
重写(Override)
父类的方法,当你需要修改从父类继承的方法的行为时,可以在子类中重写该方法,你可以使用 override
关键字来显式地表明你正在重写父类的方法。
class Animal {
makeSound(): void {
console.log("Some generic sound");
}
}
class Cat extends Animal {
override makeSound(): void { // 显式声明重写
console.log("Meow!");
}
}
const cat = new Cat();
cat.makeSound(); // 输出:Meow!
保护成员与私有成员:
- protected 成员只能在类本身及其子类中访问,而在类外部不可访问。
- private 成员只能在声明它们的类内部访问,即使子类也不能访问。
class Person {
protected name: string;
private id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
getId(): number {
return this.id; // 子类不能直接访问 id
}
}
class Employee extends Person {
constructor(name: string, id: number, private department: string) {
super(name, id);
}
getDetails(): string {
return `${this.name} works in the ${this.department}.`;
}
// 下面的代码会报错,因为 id 是私有的
// getIdFromEmployee(): number {
// return this.id;
// }
}
多态性
在 ArkTS
中,多态和静态成员是面向对象编程中的两个重要概念。它们各自提供了不同的功能来增强代码的灵活性和组织性。
多态:多态允许你使用一个统一的接口来表示不同类型的对象。这意味着你可以通过基类的引用调用派生类的方法,而实际执行的是派生类中重写的方法。这种特性极大地提高了代码的可扩展性和复用性。
class Animal {
// 基类方法
makeSound(): void {
console.log("Some generic animal sound");
}
}
class Dog extends Animal {
// 重写基类方法
override makeSound(): void {
console.log("Woof! Woof!");
}
}
class Cat extends Animal {
// 重写基类方法
override makeSound(): void {
console.log("Meow!");
}
}
function performAnimalActions(animal: Animal) {
animal.makeSound();
}
const dog = new Dog();
const cat = new Cat();
performAnimalActions(dog); // 输出:Woof! Woof!
performAnimalActions(cat); // 输出:Meow!
上面例子中,performAnimalActions
函数接受一个 Animal
类型的参数,但由于多态的存在,它可以处理任何从 Animal
继承的具体类型(如 Dog
和 Cat
),并根据具体类型调用相应的方法。
静态成员
静态成员(包括静态属性和静态方法)属于类本身而不是类的实例。这意味着你不需要创建类的对象就可以直接通过类名访问这些成员。静态成员通常用于定义那些与整个类相关的数据和行为,而不是单个对象实例。
示例:静态成员
class MyClass {
// 静态属性
static count: number = 0;
constructor() {
MyClass.count++; // 每次创建对象时增加计数
}
// 静态方法
static getCount(): number {
return MyClass.count;
}
}
const instance1 = new MyClass();
const instance2 = new MyClass();
console.log(MyClass.getCount()); // 输出:2
上面例子中,count
是一个静态属性,用来记录已经创建的MyClass
实例的数量。getCount
是一个静态方法,它返回当前的计数值。注意,我们是通过类名 MyClass
来访问静态成员的,而不是通过具体的实例。
结合多态和静态成员
虽然多态和静态成员服务于不同的目的,但在某些情况下,它们可以一起使用来实现更复杂的设计模式。
abstract class Vehicle {
static vehicleCount: number = 0;
constructor() {
Vehicle.vehicleCount++;
}
abstract start(): void;
static getVehicleCount(): number {
return Vehicle.vehicleCount;
}
}
class Car extends Vehicle {
override start(): void {
console.log("Car starting...");
}
}
class Bike extends Vehicle {
override start(): void {
console.log("Bike starting...");
}
}
const car = new Car();
const bike = new Bike();
car.start(); // 输出:Car starting...
bike.start(); // 输出:Bike starting...
console.log(Vehicle.getVehicleCount()); // 输出:2
上面示例中,我们有一个抽象基类 Vehicle
,它包含了一个静态属性 vehicleCount
和一个静态方法getVehicleCount
来跟踪所有车辆的数量。同时,每个具体的子类(如 Car
和 Bike
)都实现了 start
方法,展示了多态的使用。
总结:多态让你能够通过基类引用操作派生类对象,并根据对象的实际类型调用相应的方法。
静态成员则允许你在不创建对象的情况下,直接通过类名访问类级别的数据和方法。
这两种机制在设计灵活且高效的面向对象程序时都非常有用。
抽象类
在ArkTS
中,抽象类是一种特殊的类,它不能被直接实例化,只能被继承。抽象类通常用于定义一个通用的接口或基类,供子类实现具体的功能。通过使用抽象类,你可以强制子类实现某些方法,从而确保一致性和代码的可扩展性。
- 什么是抽象类?
抽象类是使用abstract
关键字定义的类。
它可以包含:抽象方法:没有具体实现的方法,必须由子类实现。
普通方法:有具体实现的方法,子类可以直接继承或重写。
属性:可以是普通的属性,也可以是静态属性。 - 定义抽象类
abstract class Animal { // 属性 name: string; // 构造函数 constructor(name: string) { this.name = name; } // 抽象方法:子类必须实现 abstract makeSound(): void; // 普通方法:子类可以直接继承 move(): void { console.log(`${this.name} is moving.`); } }
在这个例子中:
makeSound
是一个抽象方法,没有具体的实现。
move
是一个普通方法,提供了默认实现。
- 继承抽象类
要使用抽象类,必须创建一个子类并实现所有的抽象方法。如果子类没有实现所有抽象方法,则会报错。子类可以通过override
显式声明覆盖父类的方法。
class Dog extends Animal {
// 实现抽象方法
override makeSound(): void {
console.log("Woof! Woof!");
}
}
class Cat extends Animal {
// 实现抽象方法
override makeSound(): void {
console.log("Meow!");
}
}
// 创建对象
const dog = new Dog("Rex");
dog.makeSound(); // 输出:Woof! Woof!
dog.move(); // 输出:Rex is moving.
const cat = new Cat("Whiskers");
cat.makeSound(); // 输出:Meow!
cat.move(); // 输出:Whiskers is moving.
- 抽象类的特点
a. 不能实例化
const animal = new Animal("Generic Animal"); // 错误:无法实例化抽象类
b. 强制子类实现抽象方法 :如果子类没有实现抽象方法,编译器会报错。
class Bird extends Animal {
// 忘记实现 makeSound 方法
}
// 错误:非抽象类 'Bird' 不会实现从其基类 'Animal' 继承的抽象成员 'makeSound'
c. 可以包含普通方法和属性:抽象类不仅可以定义抽象方法,还可以包含普通方法、属性和构造函数。
abstract class Vehicle {
static wheelCount: number = 0;
constructor(protected type: string) {}
abstract startEngine(): void;
stopEngine(): void {
console.log(`${this.type} engine stopped.`);
}
}
接口
arkTs
中,接口interface
是一种用于定义对象形状的结构。接口可以帮助你描述对象应该具有哪些方法和属性,并且可以用于类型检查,确保对象符合预期的设计。
接口的基本概念
定义对象的形状:接口可以规定对象必须包含哪些属性和方法。
类型兼容性:如果一个对象满足接口的要求,它就被认为是兼容的。
可扩展性:接口可以通过继承其他接口来扩展其定义。
定义接口
使用interface
关键字来定义接口。
interface Person {
name: string;
age: number;
}
这个简单的接口Person
要求实现它的对象必须包含name
和age
属性,分别对应字符串和数字类型。
实现接口
你可以直接创建一个符合接口的对象,或者让类去实现接口。
-
直接创建对象:
const person: Person = { name: "Alice", age: 25 }; console.log(person.name); // 输出:Alice
-
类实现接口:
class Student implements Person { constructor(public name: string, public age: number) {} } const student = new Student("Bob", 20); console.log(student.name); // 输出:Bob
可选属性
有时候并不是所有的属性都是必需的,这时可以使用?
来标记属性为可选。
interface Contact {
name: string;
phone?: string; // 可选属性
}
const contact1: Contact = { name: "Charlie" }; // 合法
const contact2: Contact = { name: "Delta", phone: "123-4567" }; // 合法
只读属性
如果你希望某些属性在初始化后不能被修改,可以使用 readonly
关键字。
interface Config {
readonly apiKey: string;
}
const config: Config = { apiKey: "abc123" };
// config.apiKey = "newKey"; // 错误:无法分配到 "apiKey" ,因为它是只读属性。
函数类型
接口不仅可以定义对象的属性,还可以定义函数签名。
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function(source, subString) {
return source.search(subString) !== -1;
};
扩展接口
接口可以通过继承其他接口来扩展功能。
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
const square: Square = { color: "blue", sideLength: 10 };
console.log(square.color); // 输出:blue
console.log(square.sideLength); // 输出:10
混合类型
有时你需要一个对象既能作为一个函数调用,又能作为普通对象使用。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
const counter = <Counter>function (start: number) {};
counter.interval = 123;
counter.reset = function () {};
return counter;
}
const c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
接口与类的区别
- 接口仅描述对象的形状,不提供具体实现;而类不仅描述对象的形状,还提供了具体的实现。
- 接口支持多重继承;而类只能单继承,但可以多实现接口。
总结
- 接口是
TypeScript
中非常强大的工具,用于定义对象的结构和行为。- 使用接口可以帮助你编写更清晰、更具可维护性的代码。
- 通过接口,你可以强制要求对象遵循特定的设计模式,同时保持灵活性和扩展性。
泛型
在 ArkTS
中,泛型(Generics)
提供了一种方式来创建可以处理多种数据类型的组件(如函数、类和接口),而无需在使用时指定具体的数据类型。泛型允许你编写更加灵活和可重用的代码,并且保持类型安全。
泛型基础
最简单的泛型应用是定义一个可以处理任意类型参数的函数。
function identity<T>(arg: T): T {
return arg;
}
// 使用
let output = identity<string>("myString"); // 显式指定类型参数
console.log(output); // 输出:myString
output = identity("myString"); // 类型推断,TypeScript 自动识别类型为 string
console.log(output); // 输出:myString
在这个例子中,T
是一个占位符,代表任意类型。调用identity
函数时,你可以显式地传递类型参数,也可以让 TypeScript
自动推断类型。
泛型约束
有时你可能想要对泛型进行一些限制,例如要求传入的类型必须具有某些属性或方法。这时可以使用 泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity("hello"); // 正确,字符串有 length 属性
// loggingIdentity(42); // 错误:数字没有 length 属性
在这里,T
被约束为必须符合 Lengthwise
接口,即必须包含一个 length
属性。
泛型类
除了函数,你还可以定义泛型类。泛型类的工作方式与泛型函数类似。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 5)); // 输出:5
泛型接口
接口也可以使用泛型。这在定义服务契约或者工具函数时特别有用。
interface CreateArrayFunc<T> {
(length: number, value: T): T[];
}
let createStringArray: CreateArrayFunc<string> = function(length, value) {
let result: string[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
};
console.log(createStringArray(3, "generic")); // 输出:["generic", "generic", "generic"]
多个类型参数
有时候你需要使用多个类型参数。你可以通过在尖括号内添加更多的类型参数来实现这一点。
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, "seven"]); // 返回 ["seven", 7]
默认类型参数
你可以为泛型提供默认类型参数。
function createArray<T = string>(length: number, value: T): T[] {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
console.log(createArray(3, "default")); // 输出:["default", "default", "default"]
console.log(createArray<number>(3, 10)); // 输出:[10, 10, 10]
总结
- 泛型使你的代码更加通用和可复用,同时保持类型安全。
- 使用
<T>
或其他自定义符号作为占位符来表示类型参数。- 可以对泛型施加约束,使其只能接受特定类型的参数。
- 泛型不仅限于函数,还可以用于类和接口。
- 支持多个类型参数以及为泛型设置默认类型。
空安全性
默认情况下,ArkTS
中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript
的严格空值检查模式(strictNullChecks
),但规则更严格。
在下面的示例中,所有行都会导致编译时错误:
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?: 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
。可选链可以任意长,可以包含任意数量的?.
运算符。
模块
ArkTs
程序可以划分为多组编译单元或模块,每个模块都有自己的作用域,即在模块中创建的任何声明(变量,函数,类等)在该模块之外都不可见,除非他们被显式导出。与此相对,从另一个模块导出的变量,函数,类,接口等必须首先导入到模块中。
导出
可以使用关键之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
如果标识符列表定义了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)
。
async function test() {
let ns = await import('./say');
let hi = ns.hi;
let bye = ns.bye;
hi();
bye();
}
this关键字
关键字this
只能在类的实例方法中使用,在ArkTS
中,this
关键字很重要,它用于引用当前对象的上下文,并且在不同的场景下有不同的行为。
this的基本作用
this
是一个指向当前对象实例的引用。在类、方法或函数中,this
允许你访问当前对象的属性和方法。
示例:在类中使用this
,在这里,this
指向 Person
类的实例对象。
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name; // 使用 this 引用当前实例的属性
this.age = age;
}
greet(): void {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person("Alice", 25);
person.greet(); // 输出:Hello, my name is Alice and I am 25 years old.
this在不同上下文中的行为
-
普通函数中的
this
在普通函数中,this
的值取决于函数的调用方式。如果函数是作为对象的方法调用,则this
指向调用该方法的对象;如果函数是直接调用,则this
默认指向全局对象(浏览器中为window
,Node.js
中为global
),但在严格模式下会是undefined
。function showThis() { console.log(this); } showThis(); // 在浏览器中输出:window(非严格模式)或 undefined(严格模式)
-
箭头函数中的
this
箭头函数没有自己的this
,它的this
继承自定义时所在的上下文(通常是外层的普通函数或类)。class Counter { count: number = 0; increment = () => { this.count++; console.log(this.count); }; } const counter = new Counter(); counter.increment(); // 输出:1 counter.increment(); // 输出:2
在这里,箭头函数
increment
的this
指向Counter
的实例。 -
事件处理函数中的
this
在DOM
事件处理程序中,this
通常指向触发事件的元素。如果需要绑定到特定对象,可以使用.bind()
或箭头函数。class ButtonHandler { handleClick() { console.log(this); // 如果直接使用,this 可能指向触发事件的 DOM 元素 } bindEvents() { document.getElementById('myButton')?.addEventListener('click', this.handleClick.bind(this)); } }
this的类型检查
TypeScript
提供了对 this
的类型检查,确保你在使用 this
时不会访问不存在的属性或方法。
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getArea(): number {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 5);
console.log(rect.getArea()); // 输出:50
在这里,TypeScript
自动推断this
的类型为 Rectangle
,从而允许你安全地访问 this.width
和 this.height
。
this参数
在某些情况下,你可以显式地声明 this 的类型。这在回调函数或库设计中特别有用。
interface User {
name: string;
greet: (message: string) => void;
}
function userGreet(this: User, message: string): void {
console.log(`${this.name}: ${message}`);
}
const user: User = {
name: "Bob",
greet: userGreet
};
user.greet("Hello!"); // 输出:Bob: Hello!
在这里,this
被显式声明为 User
类型,TypeScript
会检查 this
是否符合 User
接口。
this的常见问题与解决方案
- this 的丢失问题
当将方法作为回调函数传递时,可能会导致this
的上下文丢失。class Logger { logMessage(message: string) { console.log(message); } startLogging() { setTimeout(this.logMessage, 1000); // 这里的 this 会丢失 } } const logger = new Logger(); logger.startLogging(); // 报错:Cannot read properties of undefined
解决方案一:使用箭头函数
class Logger {
logMessage = (message: string) => {
console.log(message);
};
startLogging() {
setTimeout(this.logMessage, 1000); // 箭头函数会绑定正确的 this
}
}
解决方案二:使用.bind()
方法:
class Logger {
logMessage(message: string) {
console.log(message);
}
startLogging() {
setTimeout(this.logMessage.bind(this), 1000); // 手动绑定 this
}
}
- this 的类型不匹配
如果你在方法中错误地访问了this
上不存在的属性,TypeScript
会报错。
解决方案:确保class Person { name: string; constructor(name: string) { this.name = name; } greet() { console.log(this.age); // 错误:'age' 不在 'Person' 类型上 } }
this
访问的属性或方法在类中已定义。
总结
this
是一个动态绑定的关键字,其值取决于函数的调用方式。- 在类中,
this
指向当前实例对象。- 箭头函数没有自己的
this
,它会继承定义时的上下文。TypeScript
对this
提供了强类型检查,帮助避免运行时错误。- 注意
this
的丢失问题,可以通过箭头函数或.bind()
来解决。
ArkTs
实战
ArkTS
主要负责页面上数据维护,交互,以及基础属性的使用。
ArkUI
负责页面的布局,ArkTS
负责对组件的数据,事件等等进行维护。
组件的参数
在使用ArkUI进行布局的时候,组件次啊用括号的方式来引入使用。
Column(参数) {
// 存放子元素
}
.width(100) // 这个代表属性
不是所有的组件都是可以支持参数设置,组件有参数和无参数。
Text("文本") // 组件传递了参数
Column(){} // 这个组件无参数
Column((space:10)){} // 控制子元素之间的距离
Image($r("app.media.app_icon")) // 必须传递参数
属性的概念:属性主要用于维护组建的一些样式或者事件。
Text("文字")
.fontSize(20)
.fontColor("#f3f3f3")
Text($r("app.string.mytext"))
事件绑定
import { Employee } from "../common/employee"
@Entry
@Component
struct Index {
@State message:string = "Hello World"
checkMe = ()=>{
this.message = "鸿蒙OS"
}
build() {
Column() {
Text(this.message)
Button("点击").onClick(this.checkMe)
Button("点击2").onClick((event:ClickEvent)=>{
})
TextInput({placeholder:"请输入内容"}).onBlur(()=>{
})
}
.height("100%")
.width("100%")
}
}
注意:一般绑定事件的时候,默认采用驼峰命名的方式来实现,时间函数大家尽量采用箭头函数,不要用普通函数,存在this指向的问题。
普通的函数里面,this
的指向由调用者来决定。
双向绑定
import { Employee } from "../common/employee"
@Entry
@Component
struct Index {
@State message:string = "Hello World"
checkMe = ()=>{
this.message = "鸿蒙OS"
}
build() {
Column() {
Text(this.message)
TextInput({placeholder:"请输入内容",text:$$this.message}).onBlur(()=>{
console.log("双向绑定")
})
}
.height("100%")
.width("100%")
}
}