一、泛型类型和函数

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

  1、泛型类和接口

        类和接口可以定义为泛型,将参数添加到类型定义中,如以下示例中的类型参数Element:

class CustomStack<Element> {
  public push(e: Element):void {
    // ...
  }
}

   2、泛型约束

        泛型类型的类型参数可以被限制只能取某些特定的值。例如,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();
    // ...其他...
  }
}

  3、泛型函数

        使用泛型函数可编写更通用的代码。比如返回数组最后一个元素的函数:

function last(x: number[]): number {
  return x[x.length - 1];
}
last([1, 2, 3]); // 3

        在函数调用中,类型实参可以显式或隐式设置:

// 显式设置的类型实参
last<string>(['aa', 'bb']);
last<number>([1, 2, 3]);

// 隐式设置的类型实参
// 编译器根据调用参数的类型来确定类型实参
last([1, 2, 3]);

  4、泛型默认值

        泛型类型的类型参数可以设置默认值。这样可以不指定实际的类型实参,而只使用泛型类型名称。下面的示例展示了类和函数的这一点。

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),但规则更严格。

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 */ }

  1、非空断言运算符

        后缀运算符!可用于断言其操作数为非空。

        应用于空值时,运算符将抛出错误。否则,值的类型将从T | null更改为T:

class C {
  value: number | null = 1;
}

let c = new C();
let y: number;
y = c.value + 1;  // 编译时错误:无法对可空值作做加法
y = c.value! + 1; // ok,值为2

  2、空值合并运算符

        空值合并二元运算符??用于检查左侧表达式的求值是否等于null或者undefined。如果是,则表达式的结果为右侧表达式;否则,结果为左侧表达式。

        换句话说,a ?? b等价于三元运算符(a != null && a != undefined) ? a : b。

        在以下示例中,getNick方法如果设置了昵称,则返回昵称;否则,返回空字符串:

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

 三、模块

        程序可划分为多组编译单元或模块。

        每个模块都有其自己的作用域,即,在模块中创建的任何声明(变量、函数、类等)在该模块之外都不可见,除非它们被显式导出。

        与此相对,从另一个模块导出的变量、函数、类、接口等必须首先导入到模块中。

  1、导出

        可以使用关键字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));
}

  2、导入

        1、静态导入

        导入声明用于导入从其他模块导出的实体,并在当前模块中提供其绑定。导入声明由两部分组成:

  • 导入路径,用于指定导入的模块;
  • 导入绑定,用于定义导入的模块中的可用实体集和使用形式(限定或不限定使用)。

        导入绑定可以有几种形式。

        假设模块具有路径“./utils”和导出实体“X”和“Y”。

        导入绑定* as A表示绑定名称“A”,通过A.name可访问从导入路径指定的模块导出的所有实体:

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下:

import { X as Z, Y } from './utils'
Z // 表示来自Utils的X
Y // 表示来自Utils的Y
X // 编译时错误:'X'不可见

        2、动态导入

        应用开发的有些场景中,如果希望根据条件导入模块或者按需导入模块,可以使用动态导入代码替静态导入。

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

四、关键字

        this

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

        关键字this的指向:

  • 调用实例方法的对象
  • 正在构造的对象

        使用限制:

  • 不支持this类型
  • 不支持在函数和类的静态方法中使用this
    class A {
      count: string = 'a'
      m(i: string): void {
        this.count = i;
      }
    }
    
    class A {
      n: number = 0
      f1(arg1: this) {} // 编译时错误,不支持this类型
      static f2(arg1: number) {
        this.n = arg1;  // 编译时错误,不支持在类的静态方法中使用this
      }
    }
    
    function foo(arg1: number) {
      this.n = i;       // 编译时错误,不支持在函数中使用this
    }

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐