目录

ArkTS:鸿蒙生态的现代化语言利剑

ArkTS 语法基石:从TypeScript出发

基础知识

声明

类型

运算符

语句

函数

函数声明

可选参数

Rest参数

返回类型

函数的作用域

函数调用

函数类型

Lambda函数

闭包

函数重载

声明类

创建实例

字段

方法

构造函数

可见性修饰符

对象字面量

抽象类

总结


ArkTS:鸿蒙生态的现代化语言利剑

  1. 诞生背景与核心定位

    • 解决移动开发痛点: 传统开发中逻辑与UI紧密耦合、状态管理复杂、多设备适配困难等挑战。

    • 继承与超越: 基于TypeScript (TS),提供静态类型检查、现代语法特性;深度融入鸿蒙声明式UI范式、状态管理、原生能力调用。

    • 核心目标: 提升开发效率、应用性能、代码可维护性与跨设备体验一致性。

  2. ArkTS vs TypeScript:关键增强

    • 声明式UI范式: 核心差异!ArkTS使用简洁的声明式语法描述UI(基于ArkUI框架),而非TS常用的命令式操作DOM。

    • 状态管理内置支持: 提供@State@Prop@Link@Observed@ObjectLink等装饰器,实现数据变化驱动UI自动更新。

    • 渲染控制语法: 内置if/elseForEach等条件渲染和循环渲染语法,直接在UI描述中使用。

    • 原生能力友好: 提供更符合鸿蒙API调用习惯的封装和类型定义。

    • 特定运行时与编译器: 针对鸿蒙系统进行深度优化,追求更高性能。

  3. ArkTS核心优势

    • 开发高效: 声明式UI+响应式编程大幅减少样板代码。

    • 性能卓越: 精细化UI更新、跨语言调用优化、AOT编译(部分场景)。

    • 多设备归一: 一套ArkTS代码,通过自适应布局、响应式设计、资源管理等机制,高效适配手机、平板、智慧屏、车机等多种设备。

    • 生态兼容: 兼容Web开发主流工具链(VS Code等)和部分JS/TS生态库。

ArkTS 语法基石:从TypeScript出发

基础知识

声明

声明:ArkTS通过声明引入变量、常量、类型和函数。

声明变量:let 变量名: 变量类型 = 变量值

let abc: string = "hello world";

声明常量:const 常量名: 常量类型 = 常量值,常量无法重新赋值。

const abc: string = "abc";

自动类型推动:如果变量或常量的声明包含了初始值,开发者就不需要显式指定其类型。

// 变量类型为number
let abc = 123;
// 变量类型为字符串
const cba = "cba";
类型

数据类型包括基本数据类型和引用数据类型。基本数据类型包括number、string等简单类型,它们可以准确地表示单一的数据类型。引用类型包括对象、数组和函数等复杂数据结构。

number类型:任何整数和浮点数都可以被赋给number类型变量。

// 十进制整数
let number1 = 123;
// 十六进制整数
let number2 = 0x1F;
// 八进制整数
let number3 = 0o17;
// 二进制整数
let number4 = 0b11;
// 浮点数
let number5 = 3.14;
// 指数
let number6 = 1e2;

boolean类型:由true和false两个逻辑值组成。

let isTrue: boolean = false;

string类型:代表字符序列,可以使用转义字符来表示字符。

let string1: string = "hello world\n";

void类型:void是引用类型,因此它可以用于泛型类型参数。

class Person {
    name: string
    
    setName(name: string): void {
        this.name = name;
    }
}

Object类型:Object类型是所有引用类型的基类型。任何值,包括基本类型的值,都可以直接被赋给Object类型的变量(基本类型值会被自动装箱)。Object类型用于表示除基本类型外的类型。

let object1:object = "Person";

array类型:由可赋值给数组声明中指定的元素类型的数据组成的对象。

let abc: string[] = ['abc', 'bcd', 'cdf']

enum类型:枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。

enum Colors {Red, Green, Blue};
let color: Colors = Colors.Red;

Union类型:联合类型,是由多个类型组合成的引用类型。

class Apple {
    name: string = "apple";
}

class Banana {
    name: string = "banana";
}

class Orange {
    name: string = "orange";
}

type Fruit = Apple | Banana | Orange;

Aliases类型:匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。

type Matrix = number[][];
运算符

比较运算符:

运算符 说明
=== 如果两个操作数严格相等(对于不同类型的操作数认为是不相等的,如string和number),则返回true。
!== 如果两个操作数严格不相等(对于不同类型的操作数认为是不相等的,如string和number),则返回true。
== 如果两个操作数相等,则返回true。
!= 如果两个操作数不相等,则返回true。
> 如果左操作数大于右操作数,则返回true。
>= 如果左操作数大于或等于右操作数,则返回true。
< 如果左操作数小于右操作数,则返回true。
<= 如果左操作数小于或等于右操作数,则返回true。

算数运算符:

运算符 说明
+ 加法
- 减法
* 乘法
/ 除法
% 除法后余数

位运算符:

运算符 说明
a & b 按位与:如果两个操作数的对应位都为1,则将这个位设置为1,否则设置为0。
a | b 按位或:如果两个操作数的相应位中至少有一个为1,则将这个位设置为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语句用于需要根据逻辑条件执行不同语句的场景。当逻辑条件为真时,执行对应的一组语句,否则执行另一组语句。

let a: number = 3;
let b: number = 2;
if (a > b) {
    console.info("a 大于 b")
} else if (a == b) {
    console.info("a 等于 b")
} else {
    console.info("a 小于 b")
}

switch语句:使用switch语句执行与switch表达式值匹配的代码块。

switch(light) {
    case 'red': console.log("当前是红灯")
        break;
    case 'yellow': console.log("当前是黄灯")
        break;
    case 'green': console.log("当前是绿灯")
        break;
    default: console.log("未亮灯")
}

条件表达式:根据第一个表达式的布尔值来返回其他两个表达式之一。

let result = a > b ? "Yes" : "No";

for语句:for语句会被重复执行,直到循环退出语句值为false。

let result = 0;
for (let i = 0; i < 10; i++) {
    result += i;
}

for-of语句:使用for-of语句可遍历数组、Set、Map、字符串等可迭代的类型。

for (let ch of 'hello world') {
    console.info(ch);
}

while语句:只要condition为真值(转换后为true的值),while语句就会执行statements语句。

let a = 0;
let result = 0;
while (a < 10) {
    a++;
    result += a;
}

do-while语句:如果condition的值为真值(转换后为true的值),那么statements语句会重复执行。

let a = 0;
let result = 0;
do {
    a++;
    result += a;
} while (a < 10)

break语句:使用break语句可以终止循环语句或switch。

let a = 5;
while (true) {
    a++;
    if (a > 10) {
        break;
    }
}

continue语句:continue语句会停止当前循环迭代的执行,并将控制传递给下一个迭代。

let a = 0;
let result = 0;
while (a < 20) {
    if (a % 2 == 1) {
        continue;
    }
    result += a;
}

throw和try语句:throw语句用于抛出异常或错误,try语句用于捕获和处理异常或错误。

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.info('result: ' + res);
  } catch (x) {
    console.error('some error');
  }
}

函数

函数声明

函数声明引入一个函数,包含其名称、参数列表、返回类型和函数体。

函数声明:function 函数名(参数): 返回值类型 {}

function add(num1: number, num2: number): number {
    return num1 + num2;
}
可选参数

可选参数的格式可为name?: Type。

function callname(name?: string) {
    if (name == undefined) {
        console.log('hello')
    } else {
        console.log(`hi, ${name}!`);
    }
}
Rest参数

函数的最后一个参数可以是rest参数。rest参数的格式为...restArgs。rest参数允许函数接收一个由剩余实参组成的数组,类型为任意指定类型,用于处理不定数量的参数输入。

function sum(...nums: number[]): number {
    let result = 0;
    for (let n of nums) {
        result += n;
    }
    return result;
}
sum(1);
sum(1, 2);
sum(1, 2, 3);
返回类型

如果可以从函数体内推断出函数返回类型,则可在函数声明中省略标注返回类型。

// 显示指定回返回类型
function hi(): string {
    return 'hi';
}

// 推断返回类型为string
function hello() {
    return "hello";
}
函数的作用域

函数中定义的变量和其他实例仅可以在函数内部访问,不能从外部访问。

如果函数中定义的变量与外部作用域中已有实例同名,则函数内的局部变量定义将覆盖外部定义。

let name = "Jack";
function hi() {
    let name = "Anny";
    console.log(name);
}
hi();
函数调用

调用函数以执行其函数体,实参值会赋值给函数的形参。

function add(num1: number, num2: number): number {
    return num1 + num2;
}

add(1, 2);
函数类型

函数类型通常用于定义回调函数。

type addone = (x: number) => number + 1 // 这是一个函数类型

function do_action(f: addone) {
    f(1)
}

do_action(Math.cos);
Lambda函数
let sum = (x: number, y: number): number => {
    return 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 = '';
    age: number = 0;
    
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}
创建实例

定义类后,可以使用关键字new创建实例。

let p = new Person('jack', 30)
字段

字段是直接在类中声明的某种类型的变量。类可以具有实例字段或者静态字段。

实例字段:实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。

要访问实例字段,需要使用类的实例。

class Person {
    name: string = "";
    age: number = 0;

    consturctor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    getName(): string {
        return this.name;
    }
}

let person1 = new Person('jack', 30);
person1.name;
let person2 = new Person('anny', 29);
person2.getName();

静态字段:使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。

class Person {
    name: string = '';
    age: number = 0;
    static numberofPerson = 0;

    consturctor(name: string, age: number) {
        this.name = name;
        this.age = age;
        Person.numberofPerson++;
    }
}

字段初始化:为了减少运行时错误并提高执行性能,ArkTS要求所有字段在声明时或者构造函数中显式初始化。

class Person {
    name: string = '';
    
    setName(name: string): void {
        this.name = name;
    }
    
    getName(): string {
        return this.name;
    }
}

let person = new Person();
person.getName().length;

接下来的代码展示了当name的值可以是undefined时,应该如何编写代码。

class Person {
    name?: string;

    setName(name: string): void {
        this.name = name
    }

    getName(): string | undefined {
        return this.name
    }
}

let person1 = new Person();
person1.getName()?.length();

getter和setter:setter和getter可用于提供对对象属性的受控访问。

class Person {
    name: string = '';
    private _age: number = 0;

    get age(): number { return this._age }
    set age(age: number) {
        if (age < 0) {
            throw Error('无效的年龄参数');
        }
        this._age = age;
    }
}

let p = new Person();
p.age; // 输出0
p.age = -42; // 设置无效age值会抛出错误
方法

类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。

实例方法:

class RectangleSize {
  private height: number = 0;
  private width: number = 0;
  constructor(height: number, width: number) {
    this.height = height;
    this.width = width;
  }
  calculateArea(): number {
    return this.height * this.width;
  }
}

静态方法:使用关键字static将方法声明为静态。静态方法属于类本身,只能访问静态字段。

class Cl {
  static staticMethod(): string {
    return 'this is a static method.';
  }
}
console.info(Cl.staticMethod());

继承:一个类可以继承另一个类(称为基类),继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。

class Person {
    name: string = '';
    private _age = 0;

    get age(): number {
        return this._age;
    }
}

class Student extends Person {
    score: number = 0;
    calculateScore(): number {
        return score + 100;
    }
}

父类访问:关键字super可用于访问父类的实例字段、实例方法和构造函数。

class RectangleSize {
  protected height: number = 0;
  protected width: number = 0;

  constructor (h: number, w: number) {
    this.height = h;
    this.width = w;
  }

  draw() {
    /* 绘制边界 */
  }
}
class FilledRectangle extends RectangleSize {
  color = ''
  constructor (h: number, w: number, c: string) {
    super(h, w); // 父类构造函数的调用
    this.color = c;
  }

  draw() {
    super.draw(); // 父类方法的调用
    // super.height -可在此处使用
    /* 填充矩形 */
  }
}

方法重写:子类可以重写其父类中定义的方法的实现。

class RectangleSize {
  // ...
  area(): number {
    // 实现
    return 0;
  }
}
class Square extends RectangleSize {
  private side: number = 0;
  area(): number {
    return this.side * this.side;
  }
}

方法重载签名:通过重载签名,指定方法的不同调用。

class C {
  foo(x: number): void;            /* 第一个签名 */
  foo(x: string): void;            /* 第二个签名 */
  foo(x: number | string): void {  /* 实现签名 */
  }
}
let c = new C();
c.foo(123);     // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名
构造函数

类声明可以包含用于初始化对象状态的构造函数。

class Person {
    name: string = "";
    age: number = 0;

    consturctor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    getName(): string {
        return this.name;
    }
}

派生类的构造函数:构造函数函数体的第一条语句可以使用关键字super来显式调用直接父类的构造函数。

class RectangleSize {
  constructor(width: number, height: number) {
    // ...
  }
}
class Square extends RectangleSize {
  constructor(side: number) {
    super(side, side);
  }
}

构造函数重载签名:可以通过编写重载签名,指定构造函数的不同调用方式。

class C {
  constructor(x: number)             /* 第一个签名 */
  constructor(x: string)             /* 第二个签名 */
  constructor(x: number | string) {  /* 实现签名 */
  }
}
let c1 = new C(123);      // OK,使用第一个签名
let c2 = new C('abc');    // OK,使用第二个签名
可见性修饰符

类的方法和属性都可以使用可见性修饰符。可见性修饰符包括:private、protected和public。默认可见性为public。

Public(公有):public修饰的类成员(字段、方法、构造函数)在程序的任何可访问该类的地方都是可见的。

Private(私有):private修饰的成员不能在声明该成员的类之外访问

class C {
  public x: string = '';
  private y: string = '';
  set_y (new_y: string) {
    this.y = new_y; // OK,因为y在类本身中可以访问
  }
}
let c = new C();
c.x = 'a'; // OK,该字段是公有的
c.y = 'b'; // 编译时错误:'y'不可见

Protected(受保护):protected修饰符的作用与private修饰符非常相似,不同点是protected修饰的成员允许在派生类中访问

class Base {
  protected x: string = '';
  private y: string = '';
}
class Derived extends Base {
  foo() {
    this.x = 'a'; // OK,访问受保护成员
    this.y = 'b'; // 编译时错误,'y'不可见,因为它是私有的
  }
}
对象字面量

对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。对象字面量的表示方式是:封闭在花括号对({})中的'属性名:值'的列表。

class C {
  n: number = 0;
  s: string = '';
}

let c: C = {n: 42, s: 'foo'};

Record类型的对象字面量:泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。

let map: Record<string, number> = {
  'John': 25,
  'Mary': 21,
}

map['John']; // 25
抽象类

带有abstract修饰符的类称为抽象类。抽象类可用于表示一组更具体的概念所共有的概念。

abstract class X {
  field: number;
  constructor(p: number) {
    this.field = p; 
  }
}

let x = new X(666)  //编译时错误:不能创建抽象类的具体实例

抽象类的子类可以是抽象类也可以是非抽象类。抽象父类的非抽象子类可以实例化。

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);

抽象方法:带有abstract修饰符的方法称为抽象方法,抽象方法可以被声明但不能被实现。只有抽象类内才能有抽象方法,如果非抽象类具有抽象方法,则会发生编译时错误。

class Y {
  abstract method(p: string)  //编译时错误:抽象方法只能在抽象类内。
}

总结

ArkTS不仅是鸿蒙应用开发的语言,更是开启高效、优雅开发之门的钥匙。掌握其声明式UI思想与响应式状态管理精髓,将使您在多设备鸿蒙生态开发中游刃有余。本章为您奠定了坚实的语法基础与核心概念,接下来的旅程中,我们将深入探索UI组件、布局、动画、网络、数据存储等更多精彩内容,助您快速成长为鸿蒙开发高手!拿起键盘,用ArkTS书写您的鸿蒙应用新篇章吧!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐