为什么打字稿在使用泛型时允许这种循环引用?
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 允许这样做是完全有道理的。
为什么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 允许这样做是完全有道理的。