ArkTS 基础语法

ArkTS 基础语法

基本知识

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

    • 变量声明:以关键字let开头的声明引入变量,该变量在程序执行期间可以具有不同的值。

      let str:string='hello';
      str='hello ArkTS';
      

      Harmony

    • 常量声明:以关键字const开头的声明引入只读常量,该常量只能被赋值一次。(对常量重新赋值会造成编译时错误。)

      const str:string='hello';
      // 重复赋值会报错 str='hello ArkTS';
      
    • 自动类型推断:一个变量或常量的声明包含了初始值,那么开发者就不需要显式指定其类型

      let hi1: string = 'hello';
      let hi2 = 'hello ArkTS';
      
  • 类型:

    • Number类型:任何整数和浮点数都可以被赋给此类型的变量。

      • 整数

        let num1:number=1;//十进制整数
        let num2:number=0x1123;//十六进制整数(以0x(或0X)开头的十六进制整数)
        let num3:number=0o777;//八进制整数(以0o(或0O)开头的八进制整数)
        let num4:number=0b11;//二进制整数()
        
      • 浮点数

        let num1:number=3.14159;//小数部分(由十进制数字字符串表示)
        let num2:number=.5;//小数点(“.”)
        let num3:number=1e2;//以“e”或“E”开头的指数部分,后跟有符号(即,前缀为“+”或“-”)或无符号整数。
        
    • Boolean类型:由true和false两个逻辑值组成。

      let isDone: boolean = false;
      
    • String类型:代表字符序列;可以使用转义字符来表示字符。字符串字面量由单引号(')或双引号(")之间括起来的零个或多个字符组成。字符串字面量还有一特殊形式,是用反向单引号(`)括起来的模板字面量。

      let s1 = 'Hello, world!\n';
      let s2 = 'this is a string';
      let s3 = "Success";
      let s4 = `The result is ${a}`;
      
    • Void类型:用于指定函数没有返回值。此类型只有一个值,同样是void。由于void是引用类型,因此它可以用于泛型类型参数。

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

    • Array类型:即数组,是由可赋值给数组声明中指定的元素类型的数据组成的对象。(即用方括号括起来的零个或多个表达式的列表),其中每个表达式为数组中的一个元素)来赋值。数组的长度由数组中元素的个数来确定。数组中第一个元素的索引为0。

      let names: string[] = ['Alice', 'Bob', 'Carol'];
      
    • Enum类型:又称枚举类型,是预先定义的一组命名值的值类型,其中命名值又称为枚举常量。

      //使用枚举常量时必须以枚举类型名称为前缀。
      enum ColorSet1 { Red, Green, Blue }
      let c: ColorSet1 = ColorSet1.Red;
      //常量表达式可以用于显式设置枚举常量的值。
      enum ColorSet2 { White = 0xFF, Grey = 0x7F, Black = 0x00 }
      let c: ColorSet2 = ColorSet2.Black;
      
    • Union类型:即联合类型,是由多个类型组合成的引用类型。联合类型包含了变量可能的所有类型。

      class Cat {
        sleep () {} 
        meow () {} 
      }
      class Dog {
        sleep () {} 
        bark () {}
      }
      class Frog {
        sleep () {} 
        leap () {}
      }
      type Animal = Cat | Dog | Frog
      // Cat、Dog、Frog是一些类型(类或接口)
      
      let animal: Animal = new Cat();
      animal = new Frog();
      // 可以将类型为联合类型的变量赋值为任何组成类型的有效值
      
      //可以用不同的机制获取联合类型中特定类型的值。
      function foo(animal: Animal) {
        if (animal instanceof Frog) {
          animal.leap();  // animal在这里是Frog类型
        }
        animal.sleep(); // Animal具有sleep方法
      }
      
      
    • Aliases类型:为匿名类型(数组、函数、对象字面量或联合类型)提供名称,或为已有类型提供替代名称。

      type Matrix = number[][];
      type Handler = (s: string, no: number) => string;
      type Predicate <T> = (x: T) => boolean;
      type NullableObject = Object | null;
      
  • 运算符

    • 赋值运算符

      运算符说明
      =存储第二个操作数对象的值在第一个操作数指定的对象 (简单的赋值)。
      *=用第一个操作数的值与第二个操作数对象的值;将结果存储在第一个操作数指定的对象。
      /=由另一个操作对象的值将第一个操作数的值;将结果存储在第一个操作数指定的对象。
      %=接受第二个操作对象的值指定的第一个操作数的模数;将结果存储在第一个操作数指定的对象。
      +=添加第二个操作数对象的值赋给第一个操作数的值;将结果存储在第一个操作数指定的对象。
      –=从第一个操作数的值减去第二个操作数对象的值;将结果存储在第一个操作数指定的对象。
      <<=转换第一个操作数的值将第二个操作数对象的值指定的位数;将结果存储在第一个操作数指定的对象。
      >>=转换位的数目由另一个操作对象的值指定第一个操作数权限的值;将结果存储在第一个操作数指定的对象。
      &=获取按位与第一个和第二个操作对象;将结果存储在第一个操作数指定的对象。
      ^=按位 " 异或获取第一个和第二个操作对象;将结果存储在第一个操作数指定的对象。
    • 比较运算符

      运算符说明
      ===如果两个操作数严格相等(不同类型的操作数是不相等的),则返回true。
      !==如果两个操作数严格不相等(不同类型的操作数是不相等的),则返回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语句用于需要根据逻辑条件执行不同语句的场景。

      if (condition1) {
        // 语句1
      } else if (condition2) {
        // 语句2
      } else {
        // else语句
      }
      
    • Switch语句:使用switch语句来执行与switch表达式值匹配的代码块。

      switch (expression) {
        case label1: // 如果label1匹配,则执行
          // ...
          // 语句1
          // ...
          break; // 可省略
        case label2:
        case label3: // 如果label2或label3匹配,则执行
          // ...
          // 语句23
          // ...
          break; // 可省略
        default:
          // 默认语句
      }
      
    • 条件表达式(三目表达式):由第一个表达式的布尔值来决定返回其它两个表达式中的哪一个。

      let message = isValid ? 'Valid' : 'Failed';
      
    • For语句:for语句会被重复执行,直到循环退出语句值为false。

      let sum = 0;
      for (let i = 0; i < 10; i += 2) {
        sum += i;
      }
      
    • For-of语句:使用for-of语句可遍历数组或字符串。

      // 遍历
      Maplet map = new Map([["a", 1], ["b", 2], ["c", 3]]);
      for (let value of map) {  
          console.log(value); // 分别打印 ["a", 1] ["b", 2] ["c", 3]
      }
      
    • While语句:只要condition为真值(转换后为true的值),while语句就会执行statements语句。

      let n = 0;
      let x = 0;
      while (n < 3) {
        n++;
        x += n;
      }
      
    • Do-while语句:如果condition的值为真值(转换后为true的值),那么statements语句会重复执行。

      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语句

      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}'`);
          }
        }
      }
      

函数

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

    //定义一个函数,其包含两个string类型的参数,函数返回值为string类型
    function add(x: string, y: string): string {
      let z: string = `${x} ${y}`;
      return z;
    }
    
  • 可选参数:可选参数的格式可为param?: Type。

    // 定义一个接口,其参数为可选参数
    function hello(name?: string) {
      if (name == undefined) {
        console.log('Hello!');
      } else {
        console.log(`Hello, ${name}!`);
      }
    }
    // 调用
    hello();//不使用参数
    hello('Hello ArtTs');//使用参数
    
    // 定义一个函数,可以设置其默认值,如果不使用参数,则使用默认值作为实参
    function multiply(n: number, coeff: number = 2): number {
      return n * coeff;
    }
    multiply(2);  // 返回2*2
    multiply(2, 3); // 返回2*3
    
  • Rest参数:函数的最后一个参数可以是rest参数。使用rest参数时,允许函数或方法接受任意数量的实参。即可变参数

    function sum(...numbers: number[]): number {
      let res = 0;
      for (let n of numbers)
        res += n;
      return res;
    }
    //
    sum() // 返回0
    sum(1, 2, 3) // 返回6
    
  • 返回类型:函数的返回值类型

    //1.指定返回值类型
    function foo():string{
        return "Hello";
    }
    //2.自动推断返回值类型
    function foo1(){
        return "Hello ArkTS"
    }
    //3.无返回值
    function foo2():void{
        //TODO
    }
    
  • 函数的作用域

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

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

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

    // 函数定义
    function join(x: string, y: string): string {
      let z: string = `${x} ${y}`;
      return z;
    }
    // 函数调用
    let result = join('Hello','World');
    
  • 函数类型:函数类型通常用于定义回调:

     // 定义一个函数类型
    type trigFunc = (x: number) => number
    // 定义一个函数,其参数为函数类型
    function do_action(f: trigFunc) {
       f(3.141592653589); // 调用函数
    }
    // 调用函数
    do_action(Math.sin); // 将函数作为参数传入
    
  • 箭头函数(Lambda表达式):

    // 定义一个求和箭头函数
    let sum1 = (x: number, y: number): number => {
      return x + y;
    }
    //箭头函数的返回类型可以省略;省略时,返回类型通过函数体推断。
    let sum2=(x: number, y: number)=>{
        return x+y;
    };
    // 上面函数等价于下面函数
    let sum3=(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
    
    
  • 函数重载:通过编写重载,指定函数的不同调用方式。具体方法为,为同一个函数写入多个同名但签名不同的函数头,函数实现紧随其后。(不允许重载函数有相同的名字以及参数列表,否则将会编译报错。)

    // 第一个函数定义 函数参数为Number类型
    function foo(x: number): void;   
    // 第二个函数定义 函数参数为String类型
    function foo(x: string): void;  
    // 实现函数,该函数的参数可能为以上两个函数参数的类型
    function foo(x: number | string): void {
    }
    // 调用调用
    foo(123);
    foo('aa');
    

类声明引入一个新类型,并定义其字段、方法和构造函数。

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

或者,可以使用对象字面量创建实例:

// 类定义
class Persion {
  name: string = '';
  age: number = 0;

  // 构造器
  constructor(nameParam: string, ageParam: number) {
    this.name = nameParam;
    this.age = ageParam;
  }

  // 定义一个函数,返回信息
  info(): string {
    return this.name + ' ' + this.age;
  }
}

//使用关键字new创建实例:
let p = new Persion('ArkTS', 11);
// 调用类函数
console.log(p1info());

//使用对象字面量创建实例:
class Animal {
  name: string = '';
  age: number = 0;
}

let ani: Animal = { name: 'Dog', age: 11 }
  • 字段:字段是直接在类中声明的某种类型的变量。类可以具有实例字段或者静态字段。

    • 实例字段:实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。访问实例字段,需要使用类的实例。

      class Person {
        name: string = ''
        age: number = 0
        constructor(n: string, a: number) {
          this.name = n;
          this.age = a;
        }
      
        getName(): string {
          return this.name;
        }
      }
      
      let p1 = new Person('Alice', 25);
      p1.name;
      let p2 = new Person('Bob', 28);
      p2.getName();
      
    • 静态字段:使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。要访问静态字段,需要使用类名。

      class Person {
        static numberOfPersons = 0
        constructor() {
           // ...
           Person.numberOfPersons++;
           // ...
        }
      }
      
      Person.numberOfPersons;
      
    • 字段初始化:为了减少运行时的错误和获得更好的执行性能,ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization模式一样。

      class Person {
        name: string; // undefined
        job: string = '';
        nickName?: string; //可能为 undefined
        setName(n: string): void {
          this.name = n;
        }
      
        getName(): string {
          // 开发者使用"string"作为返回类型,这隐藏了name可能为"undefined"的事实。
          // 更合适的做法是将返回类型标注为"string | undefined",以告诉开发者这个API所有可能的返回值。
          return this.name;
        }
      
        getJob(): string {
          return this.job;
        }
      
        getNickName(): string | undefined {
          return this.nickName;
        }
      }
      
      let jack = new Person();
      // 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
      jack.getName().length; // 运行时异常:name is undefined
      jack.getJob().length; //返回0,没有运行时异常
      jack.getNickName()?.length; // 编译成功,没有运行时错误
      
    • getter和setter:可用于提供对对象属性的受控访问。

      class Person {
        private _age: number = 0;
      
        get age(): number {
          return this._age;
        }
      
        set age(x: number) {
          if (x < 0) {
            throw Error('Invalid age argument');
          }
          this._age = x;
        }
      }
      let jack = new Person();
      jack.age; // 输出0
      jack.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;
        }
      }
      // 实例化
      let square = new RectangleSize(10, 10);
      // 函数调用
      square.calculateArea(); // 输出:100
      
    • 静态方法:使用关键字static将方法声明为静态。静态方法属于类本身,只能访问静态字段。

      // 定义类
      class Cl {
          // 定义一个静态方法
        static staticMethod(): string {
          return 'this is a static method.';
        }
      }
      // 静态方法调用
      console.log(Cl.staticMethod());
      
    • 继承:一个类可以继承另一个类(称为基类),并使用以下语法实现多个接口,继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。

      class Person {
        name: string = ''
        private _age = 0
        get age(): number {
          return this._age;
        }
      }
      class Employee extends Person {
        salary: number = 0
        calculateTaxes(): number {
          return this.salary * 0.42;
        }
      }
      // implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。
      interface DateInterface {
        now(): string;
      }
      class MyDate implements DateInterface {
        now(): string {
          // 在此实现
          return 'now';
        }
      }
      
    • 父类访问:关键字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,使用第二个签名
      
  • 构造函数

    // 构造函数定义语法
    constructor ([parameters]) {
      // ...
    }
    //未定义构造函数,则会自动创建具有空参数列表的默认构造函数
    class Point {
      x: number = 0
      y: number = 0
    }
    let p = new Point();
    
    
    • 派生类的构造函数:构造函数函数体的第一条语句可以使用关键字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,使用第二个签名
      
  • 可见性修饰符

    • Public(公有):public修饰的类成员(字段、方法、构造函数)在程序的任何可访问该类的地方都是可见的。
    • Private(私有):private修饰的成员不能在声明该成员的类之外访问
    • Protected(受保护):protected修饰符的作用与private修饰符非常相似,不同点是protected修饰的成员允许在派生类中访问
  • 对象字面量

    对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。

    class C {
      n: number = 0
      s: string = ''
    }
    function foo(c:C){
        
    }
    //对象字面量的表示方式是:封闭在花括号对({})中的'属性名:值'的列表。
    let c1: C = {n: 42, s: 'foo'};
    let c2: C
    
    c2 = {n: 42, s: 'foo'};  // 使用变量的类型
    foo({n: 42, s: 'foo'}); // 使用参数的类型
    
    function bar(): C {
      return {n: 42, s: 'foo'}; // 使用返回类型
    }
    
    // 也可以在数组元素类型或类字段类型中使用:
    let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}];
    
    • Record类型的对象字面量:泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值

      let map: Record<string, number> = {
        'John': 25,
        'Mary': 21,
      }
      
      map['John']; // 25
      
      //类型K可以是字符串类型或数值类型,而V可以是任何类型。
      interface PersonInfo {
        age: number
        salary: number
      }
      let map: Record<string, PersonInfo> = {
        'John': { age: 25, salary: 10},
        'Mary': { age: 21, salary: 20}
      }
      

接口

接口声明引入新类型。接口是定义代码协定的常见方式。

任何一个类的实例只要实现了特定接口,就可以通过该接口实现多态。

接口通常包含属性和方法的声明

// 接口:
interface AreaSize {
	color:string;//属性
  	calculateAreaSize(): number // 方法的声明
  	someMethod(): void;     // 方法的声明
}

// 实现:
class RectangleSize implements AreaSize {
  private width: number = 0
  private height: number = 0
  color: string = ''
  someMethod(): void {
    console.log('someMethod called');
  }
  calculateAreaSize(): number {
    this.someMethod(); // 调用另一个方法并返回结果
    return this.width * this.height;
  }
}
  • 接口属性:接口属性可以是字段、getter、setter或getter和setter组合的形式。

    //属性字段只是getter/setter对的便捷写法,以下表达方式是等价的
    interface Style0 {
      color: string
    }
    interface Style00 {
      get color(): string
      set color(x: string)
    }
    // 实现接口的类也可以使用以下两种方式:
    interface Style1 {
      color: string
    }
    
    class StyledRectangle1 implements Style1 {
      color: string = ''
    }
    
    interface Style2 {
      color: string
    }
    
    class StyledRectangle2 implements Style2 {
      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
    }
    

泛型类型和函数

泛型类型和函数允许创建的代码在各种类型上运行,而不仅支持单一类型。

  • 泛型类和接口:类和接口可以定义为泛型,将参数添加到类型定义中

    class CustomStack<Element> {
      public push(e: Element):void {
        // ...
      }
    }
    let s1 = new CustomStack<string>();
    let s2 = new CustomStack<number>();
    s1.push('hello');
    s2.push(12);
    
  • 泛型约束:泛型类型的类型参数可以被限制只能取某些特定的值。例如,MyHashMap<Key, Value>这个类中的Key类型参数必须具有hash方法。

    interface Hashable {
      hash(): number
    }
    class MyHashMap<Key extends Hashable, Value> {
      public set(k: Key, v: Value) {
        let h = k.hash();
        // ...其他代码...
      }
    }
    
  • 泛型函数:可编写更通用的代码

    // 定义泛型函数
    function last<T>(x: T[]): T {
      return x[x.length - 1];
    }
    //函数调用
    // 显式设置的类型实参
    last<string>(['aa', 'bb']);
    last<number>([1, 2, 3]);
    
    // 隐式设置的类型实参
    // 编译器根据调用参数的类型来确定类型实参
    last([1, 2, 3]);
    
  • 泛型默认值:泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。

    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>(): T {
      // ...
    }
    foo();
    // 此函数在语义上等价于下面的调用
    foo<number>();
    

空安全

默认情况下,ArkTS中的所有类型都是不可为空的,因此类型的值不能为空。这类似于TypeScript的严格空值检查模式(strictNullChecks),但规则更严格。

  • 非空断言运算符:后缀运算符!可用于断言其操作数为非空。应用于可空类型的值时,它的编译时类型变为非空类型。

    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;
    
    //eg:
    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,因为该方法可能返回null或者undefined。
      getSpouseNick(): string | null | undefined {
        return this.spouse?.nick;
      }
    
      constructor(nick: string) {
        this.nick = nick;
        this.spouse = 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”。
      
      //1.导入绑定* as A表示绑定名称“A”,通过A.name可访问从导入路径指定的模块导出的所有实体:
      import * as Utils from './utils'
      Utils.X // 表示来自Utils的X
      Utils.Y // 表示来自Utils的Y
      //2.导入绑定{ ident1, ..., identN }表示将导出的实体与指定名称绑定,该名称可以用作简单名称:
      import { X, Y } from './utils'
      X // 表示来自utils的X
      Y // 表示来自utils的Y
      //3.如果标识符列表定义了ident as alias,则实体ident将绑定在名称alias下:
      import { X as Z, Y } from './utils'
      Z // 表示来自Utils的X
      Y // 表示来自Utils的Y
      X // 编译时错误:'X'不可见
      
    • 动态导入:根据条件导入模块或者按需导入模块,可以使用动态导入代替静态导入。import()语法通常称为动态导入dynamic import,是一种类似函数的表达式,用来动态导入模块。以这种方式调用,将返回一个promise。

      // 假设say.ts模块存在以下两个方法
      export function hi() {
        console.log('Hello');
      }
      export function bye() {
        console.log('Bye');
      }
      //动态导入say.ts
      async function test() {
        let ns = await import('./say');
        let hi = ns.hi;
        let bye = ns.bye;
        hi();
        bye();
      }
      
    • 导入HarmonyOS SDK的开放能力

      • 直接导入接口模块来使用该模块内的所有接口能力

        import UIAbility from '@ohos.app.ability.UIAbility';
        
      • 导入Kit下单个模块的接口能力

        import { UIAbility } from '@kit.AbilityKit';
        
      • 导入Kit下多个模块的接口能力

        import { UIAbility, Ability, Context } from '@kit.AbilityKit';
        
      • 导入Kit包含的所有模块的接口能力

        import * as module from '@kit.AbilityKit';
        //“module”为别名,可自定义,然后通过该名称调用模块的接口。
        
  • 顶层语句

    模块可以包含除return语句外的任何模块级语句。

    如果模块包含主函数(程序入口),则模块的顶层语句将在此函数函数体之前执行。否则,这些语句将在执行模块的其他功能之前执行。

  • 程序入口:程序(应用)的入口是顶层主函数。主函数应具有空参数列表或只有string[]类型的参数。

    function main() {
      console.log('this is the program entry');
    }
    

关键字

  • this:关键字this只能在类的实例方法中使用。

    class A {
      n: number = 0
      m(i: number): void {//只能在类的实例方法中使用
        this.n = i;
      }
      f1(arg1: this) {} // 编译时错误,不支持this类型
      static f2(arg1: number) {
        this.n = arg1;  // 编译时错误,不支持在类的静态方法中使用this
      }
    }
    
    function foo(arg1: number) {
      this.n = i;       // 编译时错误,不支持在函数中使用this
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值