声明
在ArkTS中通常使用let,const分别是声明变量和常量,顾名思义变量可以在程序中对值进行修改而常量只能只读。
//变量声明
let hi: string = 'hello';
hi = 'hello, world';
//常量声明
const index: number = 1;
需要注意的是在ArkTS中进行打印只能是字符串比如
console.log(index.toString())
console.log(`下标${index}`)
自动类型推断
ArkTS是一门静态语言所有数据类型都会在编译时确定。
如果在代码中声明了 变量或者常量但是没有指定类型,ArkTS会自动推断合适的类型。
let url:string = 'www';
let url = 'www';
类型
let n:number = 1;//数值类型
let t:boolean = true;//布尔类型
let s:stringh = '1';//字符串类型类型
//Void
//一般是只函数没有指定的返回类型
function app():viod{console.log(s)}
//亦或者 在泛型类的案例中使用
class Class<T> {
//...
}
let instance: Class <void>
//对象
class Person {
name:string='小达'
age:number= 1
constructor(n: string, a: number) { this.name = n this.age = a }
}
let person = new Person('小熊',23);
//在ArkTS中需要注意的是,在对 对象进行输出打印时需要转为字符串类型,其次ArkTS不支持在运行的时候修改对象布局
//例如在TS中可以通过以上同样的代码可以,但是在ArkTS中会报错。
person.gender = 0;
//数组
let names: string[] = ['Alice', 'Bob', 'Carol'];
//枚举类型
//枚举类型 说白话就是咱们提前规定好类型,后续对变量进行赋值都必须在规定的类型范围内
//好处提高代码可读性,类型安全以及在运行的时候避免了非法值得出现提高了程序得健壮性
enum ColorSet {
red,
green,
bule
}
let color:ColorSet = ColorSet.red;
//联合类型 union
class car{}
class dog{}
type Animal = car | dog | number
let animal:Animal = new car();
animal = 42;
animal = new dog();
// 可以将类型为联合类型的变量赋值为任何组成类型的有效值
运算符
语句
//if语句
if(type==1){
...
}else if(type==2){
...
}else{
...
}
//switch语句
switch (expression) {
case label1: // 如果label1匹配,则执行
// ...
// 语句1
// ...
break; // 可省略 如果这里没有break 则是会继续执行后面代码块
case label2:
case label3: // 如果label2或label3匹配,则执行
// ...
// 语句23
// ...
break; // 可省略
default:
// 默认语句
}
//条件表达式
let age = type?18:17;
//for 语句
for(let i = 0 ; i <= 20 ; i++ ){
....
}
//for of 语句 可以遍历数组或者字符串
for (let ch of 'a string object') {
/* process ch */
}
//While语句
let a = 0 ;
// a>=3 就会停止运行
while(a<3){
a++;
}
//Do-while 同理 但是先执行逻辑代码
do{
a++
}while(a<3)
//还可以使用break; 来跳出重复执行
//如果break语句后带有标识符,则将控制流转移到该标识符所包含的语句块之外。
let x = 1
label: while (true) {
switch (x) {
case 1:
// statements
break label; // 中断while语句
}
}
//Continue语句
//在for循环语句中可以使用continue来终止语句的循环 如下
let n:number = 0;
for(let i = 0 ;i <= 20 ; i++){
if(n==2){
//到这里 for循环就会跳出循环
continue
}
n++
}
//Throw和Try语句
class ZeroDivisor extends Error {}
function divide (a: number, b: number): number{
if (b == 0) throw new ZeroDivisor();//通过throw 抛出错误
return a / b;
}
function process (a: number, b: number) {
try {
let res = divide(a, b);
//如果divide函数正常执行正常输出
console.log('result: ' + res);
} catch (x) {
//如果为 b==0 就会输出以下代码
console.log('some error');
}
}
函数
函数声明
// 这里是表述函数返回内容是字符串类型 如果函数没有返回类型 则使用void或者不写
function app(name:string):string(){
return name
}
//在函数声明的时候每个参数都需要标记类型
//如果想表述参数 非必传则可以如下
function app(name?:string):string{
if(name==undefined){
return 'undefined'
}else{
return name
}
}
默认值
//提一嘴如果想返回不同类型 也可以通过泛型来写
function app(name:string,age:number=2):string | number {
console.log(`${name}|${age}`)
if(age>=18){
return name
}else{
return age
}
}
app('达');//达|2
app('达',18);//达|18
Rest参数
函数的最后一个参数可以是rest参数。使用rest参数时,允许函数或方法接受任意数量的实参。
function app(...numbers:number[]):number{
let i = 0 ;
for(let n of numbers){
i+=n
return i;
}
}
app();//0
app(1,2);//3
返回类型
之前说过ArkTS会在自行判断合适的类型如以下情况
funciton app(name:string):string{
return name
}
//自行推断
// ↓这个地方就不用写入:Type
funciton app(name:string){
return name
}
函数的作用域
函数体内部的声明变量或者常量不能被外部访问 如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。
函数调用
上文已经提到过一种调用方法 app()这样去调用一个函数,另一种如下
function app(name:string){
return name;
}
let a = app('小达');
console.log(a);//小达
函数类型
//类型自定义命名
type num = (x:number):number=>number ;
function p(f:num):number{
f(3.14)
}
p(Math.sin);
//这样写法就等于
Matg.sin(3.14);//如有错误请纠正
箭头函数
如上文自定义命名的type就是 箭头函数 箭头函数的返回类型可以省略;省略时,返回类型通过函数体推断。 表达式可以指定为箭头函数,使表达更简短,因此以下两种表达方式是等价的:
let sum1 = (x: number, y: number) => { return x + y; }
let sum2 = (x: number, y: number) => x + y
闭包
闭包是两个函数组成一个在外部一个在内部,由内部函数对外部函数的变量进行应用导致无法释放形成的一种结构被称为闭包。
在下例中,f函数返回了一个闭包,它捕获了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,使用第二个定义
类
声明创建一个类
class Person{
name:string = '';//字段
surname:string = '';
//构造函数
constructor(n:string,sn:string){
this.name = n ;
this.surname = sn ;
}
//自定义方法
fullNmae():srting{
return this.name+''this.surname
}
}
//创建实例
let a = new Preson('da','xiong')
a.fullName();//daxiong
//创建字面量实例
let a:Person = {name:'da',surname:'xiong'};
静态字段
在字段前添加static声明为静态字段,只能通过类名本身来访问,不能被实例修改或者通过实例访问
class obj{
static a = 0;
consturctor(){}
get(){
return a
}
}
obj.a;//0
obj.get();//0
字段初始化
为了减少运行时的错误和获得更好的执行性能
class obj {
name:string
get(){
//这里name有可能是undefined
return this.name
}
}
let a = new obj();
a.get().length; //这就是就会报错
如果想代码不报错可以这样写
class obj {
name?:string
get():string | undefined {
return this.name
}
}
let a = new obj();
a.get()?.length; //这样这里就会正常编译
getter和setter
可以在访问或者修改的时候,可以根据需求进行限制
class obj {
name:string = '';
age:number = 1 ;
//访问
get get_age():{return this.age}
//修改
set set_age(a:number):number{
if(a<18){
throw Error();
}
this.age = a
}
}
let a = new obj();
a.age(); // 输出1
a.age=20;
方法
类可以定义静态方法和实例方法,静态方法只能通过类本身去访问且静态方法只能访问静态字段也可以叫静态成员,而实例方法都可以访问。
class obj{
name:string = '';
age:number = 1 ;
static gender:number = 0;
constructor(n:string,a:number){
this.name = n
this.age = a
}
//实例方法
get_serinfo():string | number{
return `${this.name}+${this.age}`
}
//静态方法
static get_gender():number{
return this.gender ;
}
}
let a = new obj('da',12);
a.get_serinfo();//da+12
obj.get_gender();//0
继承
子类继承父类可以继承字段和方法不能继承构造函数,子类可以新增字段和方法,方法可以重名重新定义。
class father{
name:string = '小头爸爸'
age:number = 44;
constructor(n:string,a:number){
this.name = n;
this.age = a;
}
getUserinfo():string{
return `${this.name}+${this.age}`
}
}
//通过extends指定son类继承extends类
class son extends father{
name:string = '大头儿子'
age:number = 10
}
//包含implements子句的类必须实现列出的接口中定义的所有方法
interface DateInterface {
now(): string;
}
class MyDate implements DateInterface {
now(): string {
// 在此实现
return 'now';
}
}
父类访问
通过关键字super可以调用父类的实例字段,实例方法,构造函数。
class father{
name:string = '小头爸爸'
age:number = 44;
constructor(n:string,a:number){
this.name = n;
this.age = a;
}
getUserinfo():string{
return `${this.name}+${this.age}`
}
}
//通过extends指定son类继承extends类
class son extends father{
gender:number=1;
constructor(n:string,a:number,g:number){
super(n,a)
this.gender = g
}
getUserinfo():string{
super.getUserinfo();
//还可以这样访问父类的实例字段
}
}
方法重写
方法重写就是子类可以覆盖父类同名的实例方法,但是需要注意的是必须具有与原始方法相同的参数类型和相同或派生的返回类型
class RectangleSize {
// ...
area(): number {
// 实现
return 0;
}
}
class Square extends RectangleSize {
//只能访问
private side: number = 0
area(): number {
return this.side * this.side;
}
}
方法重载签名
通过传入不同类型的参数调用对应的方法
class obj {
foo(x:string){}
foo(x:number){}
foo(x:number|string){}
}
let a = new obj();
a.foo('a');//a
a.foo(1);//1
可见性修饰符
类的方法和属性可以使用可见性修饰符
可见性修饰符包括:private、protected和public。默认可见性为public
public
public(公有) 字段 方法 构造函数 只要在程序中能访问到类的地方都是可访问的。
Private 私有
私有修饰符只能通过这个字段的类 可以访问,不能通过创建的实例所访问,顺便一提的是Private和static的区别,前者不能被派生类(子类)访问或者是继承。后者可以通过派生类继承或者访问,Private字段可以被实例方法修改 变量值但是该实例方法只能被当前类调用不能被创建的实例调用,static也可以修改变量值但是必须是同样的static静态方法才能修改静态成员。
其实大家看到这里应该不用代码展示区分两者区别了吧。
class obj {
Private name:string = ''
}
let a = new obj();
a.name;//这样会失败
Protected
Protected受保护,和private相似不同点在于 可以被派生类访问。
class obj {
Private name:string = ''
Protected age:number = 1
}
let a = new obj();
a.name;//这样会失败
class _obj extends obj {
foo(){
console.log(`${this.age}`)//1 给age赋值也可以哈
}
}
对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。
class C {
n: number = 0
s: string = ''
}
let c: C = {n: 42, s: 'foo'};
//函数
funciton foo (c:C){};
foo({n: 42, s: 'foo'});
//数组
let arr:C[]=[{n: 42, s: 'foo'},{n: 42, s: 'foo'}]
Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值.
let map: Record<string, number> = {
'John': 25,
'Mary': 21,
}
map['John']; // 25
接口
接口是定义代码协定的常见方式 类可以通过 implements 关键字来实现一个接口,该类就必须实现接口中的方法和字段且必须类型一致。
interface Color {
color:string
get():string
}
class box implements Color {
color:string = 'red'
get():string {
return this.color
}
}
接口属性
接口属性可以是字段、getter、setter或getter和setter组合的形式。
interface Color {
get color():string
set color(x:string)
}
class box implements Color {
private _color: string = ''
get color():string {
return this._color
}
set color(x:string){
this._color = x
}
}
接口继承
接口可以继承另外一个接口
interface Style{
color:string
}
interface ExtendedStyle extends Style {
width: number
}
如果一个类继承这个接口 ExtendedStyle 就需要同时拥有两个字段 color width
泛型类型和函数
泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型,可以根据自己需要传入符合自己需求的类型。
泛型类和接口
类和接口可以定义为泛型,将参数添加到类型定义中
class style<T>{
boxStyle(x:T){
console.log(`${x}`)
}
}
//指定传入类型 string 指定什么传什么别瞎搞
let box = new style<string>()
box.boxStyle('1920px*1080px')
泛型约束
泛类型的类型参数可以被限制在规定的范围内
interface Style{
color:string
}
class box <key extends Style,Value>{
//参数k必修符合接口Style的规定
boxStyle(k:key,v:Value):vodi{
console.log(k.color+''+v)
}
}
泛型函数
使用泛型函数可以编写更通用的代码
function last<T>(x: T[]): T {
return x[x.length - 1];
}
// 显式设置的类型实参
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]);
// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
last([1, 2, 3]);
泛型默认值
泛型默认是可以在我们规定类型之前可以默认一直类型
funticon app< T = styring>(x:T):T{
return x
}
//默认是string改为number
app<number>(1);
空安全
默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(strictNullChecks),但规则更严格。
let a:number = null ; //这样会报错
//通过联合类型我没可以规避
let a:number | null = null ;
非空断言运算符
后缀运算符!可用于断言其操作数为非空
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。
class obj {
width:number | null = null
getWidth():string {
//如果width null或者false 则返回空字串
return this.width ?? '';
}
}
可选链
可选链的语法是在点操作符(.)前加上一个问号(?),形成?.。这样,如果左侧的表达式为null或undefined,则整个表达式的值就是undefined,而不会抛出错误。
class obj{
color:string | null = null
width?:obj
setWidth(width:obj):void{
this.width = width
}
getWidth():string | null | undefined {
return this.width?.nick;
}
constructor(color:string){
this.color = color
this.width = undefined
}
}
模块
程序可划分为多组编译单元或模块。
每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。
与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。
导出
可以使用关键字export导出顶层的声明。
未导出的声明名称被视为私有名称,只能在声明该名称的模块中使用。
注意:通过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可访问从导入路径指定的模块导出的所有实体:
//表述所有模块别名 Utils
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下:
//表述X在当前页面叫做Z
import { X as Z, Y } from './utils'
Z // 表示来自Utils的Z
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见
动态导入
动态导入的一个常见用例是在某些条件下选择性地加载模块,这对于懒加载或按需加载功能特别有用,可以减少初始加载时间和提高性能。
import()语法通常称为动态导入dynamic import,是一种类似函数的表达式,用来动态导入模块。以这种方式调用,将返回一个promise。
如下例所示,import(modulePath)可以加载模块并返回一个promise,该promise resolve为一个包含其所有导出的模块对象。该表达式可以在代码中的任意位置调用。
let modulePath = prompt("Which module to load?");
import(modulePath)
.then(obj => <module object>)
.catch(err => <loading error, e.g. if no such module>)
如果在异步函数中,可以使用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();
}
更多的使用动态import的业务场景和使用实例见动态import。
导入HarmonyOS SDK的开放能力
//导入Kit下单个模块的接口能力
import { UIAbility } from '@kit.AbilityKit';
//导入Kit下多个模块的接口能力
import { UIAbility, Ability, Context } from '@kit.AbilityKit';
//导入Kit包含的所有模块的接口能力
import * as module from '@kit.AbilityKit';
//其中,“module”为别名,可自定义,然后通过该名称调用模块的接口。
//一般情况下别用这种方式引入会添加很多没有必要的模块
顶层语句
顶层语句是指在模块的最外层直接编写的语句,这些语句不被包裹在任何函数、类、块级作用域中。顶层语句包括变量声明、函数声明、表达式等。