Arkts语法<入门基础>
Arkts基础-学习语法前需掌握
都知道ArkTS是在保留TypeScript(简称TS)基本语法风格的基础上,进一步通过规范强化了静态检查和分析,使得开发者在程序开发阶段能够检测出更多错误,提升程序的稳定性和运行性能。那么对于没有TS语法学习经验的人来说,刚开始语法不是很习惯,或者说很难理解的,这里就对ArkTS一些常用语法做一些说明,帮助大家快速上手。
声明
变量:
变量是指可以在程序执行期间具有不同的值
let hi: string = 'hello';
hi = 'hello, world';
常量:
常量是指只读类型,只能被赋值一次。
const hello: string = 'hello';
hello = 'hello, world'
这个时候会提示编译时错误:Cannot assign to ‘hello’ because it is a constant.(无法将值分配给“hello”,因为它是一个常量。)
当我们没有显式指定类型,ArkTS会自动推断类型。如:
let hi2 = 'hello, world';
这个时候会推断hi2为string类型。
类型
1、基本类型
1.1、number
let n1 = 3;
let n2 = 3.141592;
let n3 = 0.5;
let n4 = 1e2;
整数和浮点数都可以被赋给此类型的变量,类型范围为(-9007199254740991~9007199254740991),基本满足日常开发需要。如果需要更大的数据类型,那么可以使用下面的类型。
1.2、BigInt
let bigInt1: BigInt = BigInt('999999999999999999999999999999999999999999999999999999999999');
let bigInt2:BigInt = 0n;
以上两种写法都能正确的声明BigInt类型。
1.3、boolean
由true和false两个逻辑值组成。
let isDone: boolean = false;
if (isDone) {
console.info('Done!');
}
1.4、string
代表字符序列,可以使用转义字符来表示字符。字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。
let s1 = 'Hello, world!\n';
let s2 = 'this is a string';
let a = 'Success';
let s3 = `The result is ${a}`;
1.5、void
void类型用于指定函数没有返回值。此类型只有一个值,同样是void。由于void是引用类型,因此它可以用于泛型类型参数。这个类型在定义接口的时候经常用到。
class Class<T> {
}
let instance: Class<void>;
2、引用类型:
2.1、Object
Object类型是所有引用类型的基类型。任何值,包括基本类型的值,都可以直接被赋给Object类型的变量(基本类型值会被自动装箱)。
let o1: Object = 'Alice';
let o2: Object = ['a', 'b'];
let o3: Object = 1;
let o4: object = [1, 2, 3];
2.2、array
数组类型,是由可赋值给数组声明中指定的元素类型的数据组成的对象。
let names: string[] = ['Alice', 'Bob', 'Carol'];
2.3、enum
枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。
enum ColorSet { Red, Green, Blue }
let c: ColorSet = ColorSet.Red;
2.4、Union
联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。
class Cat {
name: string = 'cat';
}
class Dog {
name: string = 'dog';
}
class Frog {
name: string = 'frog';
}
type Animal = Cat | Dog | Frog | number | string | null | undefined;
let animal: Animal = new Cat();
animal = new Frog();
animal = 42;
animal = 'dog';
animal = undefined;
可以使用不同机制获取联合类型中的特定类型值。
class Cat { sleep () {}; meow () {} }
class Dog { sleep () {}; bark () {} }
class Frog { sleep () {}; leap () {} }
type Animal = Cat | Dog | Frog;
foo(animal: Animal) {
if (animal instanceof Frog) { // 判断animal是否是Frog类型
animal.leap(); // animal在这里是Frog类型
}
animal.sleep(); // Animal具有sleep方法
}
如果以上代码没有看明白,那么可以看一下,下面的简单使用:
let value: string | number;
value = 'hello';
value = 123;
当使用联合类型的时候,单个变量可以拥有多个类型。那么结合上面的代码,会联想到重载这个概念,那么在Arkts中有没有重载这个概念呢?我们先看一下java的重载实现:
class Example {
void print(int value) {
System.out.println("Integer: " + value);
}
void print(String value) {
System.out.println("String: " + value);
}
}
Arkts的重载实现:
foo(x: number): void;
foo(x: string): void;
foo(x: number | string): void {
}
foo(123);
foo('aa');
根据代码我们不难看出Arkts重载实现只是定义了两个函数,而真正的函数实现只有一个,并且实现函数需兼容所有重载签名,通常需要在函数体内手动判断参数类型。这时候再看最开始的两个方法是不是就理解了。
2.5、Aliases
Aliases类型为匿名类型(如数组、函数、对象字面量或联合类型)提供名称,或为已定义的类型提供替代名称,即类型别名,使用关键字type进行声明。
// 二维数组类型
type Matrix = number[][];
const gameBoard: Matrix = [
[1, 0],
[0, 1]
];
// 函数类型
type Handler = (s: string, no: number) => string;
const repeatString: Handler = (str, times) => {
return str.repeat(times);
};
console.info(repeatString('abc', 3)); // 'abcabcabc'
// 泛型函数类型
type Predicate<T> = (x: T) => boolean;
const isEven: Predicate<number> = (num) => num % 2 === 0;
// 可为空的对象类型
type NullableObject = Object | null;
class Cat {}
let animalData: NullableObject = new Cat();
let emptyData: NullableObject = null;
运算符
1、比较运算符
需要注意的是:=== / ==
// ==只比较目标的值相等
console.info(String(null == undefined)); // true
// ===比较目标的值和类型都相等
console.info(String(null === undefined)); // false
其他运算符与java等语言基本相同,这里不过多赘述。
函数
1、声明一个函数
add(x: string, y: string): string {
let z: string = `${x} ${y}`;
return z;
}
2、可选参数
hello(name?: string) {
if (name == undefined) {
console.info('Hello!');
} else {
console.info(`Hello, ${name}!`);
}
}
如果一个函数拥有参数有值和没有值两种执行逻辑,这时候就可以用可选参数,参数用name?: Type的格式。除上面这种写法之外,还有一种默认参数值的写法:
multiply(n: number, coeff: number = 2): number {
return n * coeff;
}
multiply(2);
multiply(2, 3);
如果在函数调用的时候,默认值参数省略,那么将会使用参数的默认值为实参,也就是multiply(2)实际返回结果是2*2.
3、rest参数
rest参数允许函数接收一个不定长数组,用于处理不定数量的参数输入。格式为…restName: Type[]。需要注意,函数的最后一个参数可以是rest参数,这是什么意思呢,请看下面代码:
// 正确示范 1
sum(...numbers: number[]): number{
let res = 0;
for (let n of numbers) {
res += n;
}
return res;
}
// 正确示范 2
sum(name:string, ...numbers: number[]): number{
let res = 0;
for (let n of numbers) {
res += n;
}
return res;
}
// 错误写法,提示错误:A rest parameter must be last in a parameter list. (rest参数必须位于参数列表的最后。)
sum(...numbers: number[], name:string): number{
let res = 0;
for (let n of numbers) {
res += n;
}
return res;
}
4、函数类型
通常用于定义回调函数。
export type trigFunc = (x: number) => number // 这是一个函数类型
export class Test {
static do_action(f: trigFunc) {
const result = f(3); // 调用函数
console.log("回调函数执行结果:", result);
}
static test(){
Test.do_action( (x) =>{
return x * 2
})
}
}
// 在Index中调用
aboutToAppear(): void {
Test.test();
}
输出结果为:6;那么为什么会输出6呢,我们跟着代码来理一下。
在do_action函数中,trigFunc被作为参数使用,然后对f进行赋值3,当调用do_action方法时,x * 2其实就已经变成3*2,所以结果为6。
5、箭头函数(Lambda函数)
let sum = (x: number, y: number): number => {
return x + y;
}
类
class Person {
name: string = '';
surname: string = '';
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
fullName(): string {
return this.name + ' ' + this.surname;
}
}
基础写法如上,constructor为构造函数,其他相关知识与java等语言相视,这里不做过多解释。需要详细讲解的内容如下。
1、对象字面量
对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。表示方式是:封闭在花括号对({})中的’属性名:值’的列表。
class C {
n: number = 0;
s: string = '';
}
let c: C = {n: 42, s: 'foo'};
需要注意的是,对象字面量只能在可以推导出该字面量类型的上下文中使用。
1.2、Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。类型K可以是字符串类型或数值类型(不包括BigInt),而V可以是任何类型。
interface PersonInfo {
age: number;
salary: number;
}
let map: Record<string, PersonInfo> = {
'John': { age: 25, salary: 10},
'Mary': { age: 21, salary: 20}
}
2、抽象类
抽象类的子类可以是抽象类也可以是非抽象类。抽象父类的非抽象子类可以实例化。
abstract class Base {
field: number;
constructor(p: number) {
this.field = p;
}
}
class Derived extends Base {
constructor(p: number) {
super(p);
}
}
let x = new Derived(666);
2.1、抽象方法
只有抽象类内才能有抽象方法,带有abstract修饰符的方法称为抽象方法,抽象方法可以被声明但不能被实现。
class Y {
abstract method(p: string) //编译时错误:抽象方法只能在抽象类内。
}
抽象类和抽象方法存在的意义是:定义统一的 “行为规范”(抽象方法),约束所有子类必须遵循这个规范,同时封装子类的通用逻辑(抽象类的普通方法),实现 “规范统一、逻辑复用、差异化实现”。
接口
我们都知道在Java中接口的作用是为了实现解耦和统一规范,在Arkts中也有接口的存在,其作用与Java相同。
接口示例:
interface Style {
color: string; // 属性
}
interface AreaSize {
calculateAreaSize(): number; // 方法的声明
someMethod(): void; // 方法的声明
}
实现接口类的示例:
// 接口:
interface AreaSize {
calculateAreaSize(): number; // 方法的声明
someMethod(): void; // 方法的声明
}
// 实现:
class RectangleSize implements AreaSize {
private width: number = 0;
private height: number = 0;
someMethod(): void {
console.info('someMethod called');
}
calculateAreaSize(): number {
this.someMethod(); // 调用另一个方法并返回结果
return this.width * this.height;
}
}
1、接口继承
接口可以继承其他接口,示例如下:
interface Style {
color: string;
}
interface ExtendedStyle extends Style {
width: number;
}
继承接口包含被继承接口的所有属性和方法,此外还可以添加自己的属性和方法。
泛型类型和函数
1、泛型类和接口
类和接口可以定义为泛型,将参数添加到类型定义中。
class CustomStack<Element> {
public push(e: Element):void {
// ...
}
}
要使用类型CustomStack,必须为每个类型参数指定类型实参:
let s = new CustomStack<string>();
s.push('hello');
1.2、泛型约束
泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法,Key类型扩展了Hashable,Hashable接口的所有方法都可以为key调用。
interface Hashable {
hash(): number;
}
class MyHashMap<Key extends Hashable, Value> {
public set(k: Key, v: Value) {
let h = k.hash();
}
}
2、泛型函数
使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:
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];
}
2.1、泛型默认值
泛型类型的类型参数可以设置默认值,这样无需指定实际类型实参,直接使用泛型类型名称即可。
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>(): void {
// ...
}
foo();
// 此函数在语义上等价于下面的调用
foo<number>();
空安全
默认情况下,ArkTS中的所有类型都不允许为空,但是可以为空值的变量定义为联合类型T | null。
let x: number = null; // 编译时错误
let y: string = null; // 编译时错误
let z: number[] = null; // 编译时错误
let x: number | null = null; // 这种联合类型是允许的
x = 1; // ok
x = null; // ok
if (x != null)
1、非空断言运算符
后缀运算符!可用于断言其操作数为非空。
class A {
value: number = 0;
}
function foo(a: A | null) {
a.value; // 编译时错误:无法访问可空值的属性
a!.value; // 编译通过,如果运行时a的值非空,可以访问到a的属性;如果运行时a的值为空,则发生运行时异常
}
通过上面代码我们知道,当使用!进行非空断言时,如果变量为空,对其属性进行访问时,会出现运行时异常,所以在开发中不建议使用非空断言(有此业务/编码需求的除外)。
2、空值合并运算符
空值合并二元运算符??用于检查左侧表达式的求值是否等于null或者undefined。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。(a ?? b等价于三元运算符(a != null && a != undefined) ? a : b)
class Person {
// ...
nick: string | null = null;
getNick(): string {
return this.nick ?? '';
}
}
3、可选链
访问对象属性时,如果属性是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;
}
}
更多推荐



所有评论(0)