为什么打字稿在使用泛型时允许这种循环引用?

Why does typescript allow this kind of circular reference when using generic types?

为什么typescript不报错下面Document接口的定义。这似乎是一个循环引用。在编写 Document 接口的定义时如何提供“Document”类型参数?我将如何创建一个 Document 对象(似乎是一个无限循环引用)

 interface ValueObject<T> {
  value: T;
  validate(): Boolean
}

interface Document extends ValueObject<Document> {
    value: Document
}

找到编译的实现很简单(我将 Document 重命名为 FooDocument 以避免 browser-side Document 类型)

 interface ValueObject<T> {
  value: T;
  validate(): Boolean
}

interface FooDocument extends ValueObject<FooDocument> {
    value: FooDocument
}

class Foo implements FooDocument {
  value: FooDocument;

  constructor() {
    this.value = new Foo();
  }

  public validate() {
    return true;
  }
}

现在,这显然将在创建新 Foos 的无限循环中结束。但是,请考虑这个略有改动的示例:


class Foo implements FooDocument {
  value: FooDocument;

  constructor(x?: FooDocument) {
    if (x == undefined) {
      this.value = new Foo(this);
      console.log(1);
    } else {
      this.value = x;
      console.log(2);
    }
  }

  public validate() {
    return true;
  }
}

new Foo();

现在调用Foo构造函数不带参数,然后又调用带参数的构造函数,这样最后构造函数被调用了两次。因此,该程序不仅满足您的类型定义,而且不会无限循环地终止。

如果你稍微修改一下,你会发现这实际上与链表非常相似:

 interface CoolList<T, S> {
  next: T | undefined;
  value: S;
}

interface FooDocument extends CoolList<FooDocument, number> {
    next: FooDocument | undefined;
    value: number;
}

class Foo implements FooDocument {
  value: number;
  next: FooDocument | undefined;

  constructor(val: number, next?: FooDocument) {
    this.value = val;
    this.next = next;
  }

  
}

const last = new Foo(3);
const second = new Foo(2, last);
const first = new Foo(1, second);

console.log(first.next?.next?.value); // 3

这些通常称为递归数据类型。另一个例子是环 buffers/circular 列表(其中最后一个 next 将指向第一个元素,而不是未定义的)或树,其中您将有多个 next 属性。因此,Typescript 允许这样做是完全有道理的。