• 小记 TypeScript 中的循环引用问题


    转载至:https://blog.csdn.net/tkokof1/article/details/108984865

    平时编写 TypeScript 代码时,一般都倾向于使用模块(Module),通过结合使用 import 和 export 我们便可以方便的进行模块的导入和导出.

    举个简单的例子,假设我们有以下的 TypeScript 代码文件(A.ts):

    export class A {
    // methods here
    }


    可以看到,上述代码使用 export 导出了类型 A,如果我们需要在另外的 TypeScript 代码文件(B.ts)中使用类型 A,我们可以直接使用 import :

    import { A } from "./A.ts"
    
    class B {
    // use A here
    }

    接着我们让代码变的复杂一些,假设现在类型 A 也要使用类型 B 了,那么相关的代码可能会变成这样:

    import { B } from "./B.ts"
    
    export class A {
    // use B here
    }
    import { A } from "./A.ts"
    
    export class B {
    // use A here
    }

    此时,类型 A 与 类型 B 便产生了循环引用,一般来讲是应该尽量避免的,但是在较大型的项目中往往又很难规避,所以我们需要一种可以处理循环引用问题的方法(之前关于这个话题自己也写过一篇博文),而实际上,TypeScript 中的 import 和 export 是可以处理循环引用的:

    当 import 遇到导入完毕或者说正在导入的模块(文件)时,是直接返回导入结果的(尽管这个结果可能是不完整的),而不是递归的进行模块的导入操作,还是拿上面的代码举例,假设我们首先导入 A 模块:

    A 模块尝试导入 B 模块
    由于 B 模块尚未导入,程序开始导入 B 模块
    B 模块尝试导入 A 模块
    由于 A 模块正在导入,所以程序直接返回当前导入结果(尽管当前结果是不完整的)
    将类型 B 加入到 B 模块的导出数据中(export class B)
    B 模块导入完成,继续 A 模块的导入
    将类型 A 加入到 A 模块的导出数据中(export class A)
    A 模块导入完成
    值得注意的是,上述的这种循环引用处理方式是不完备的,该方式并不能正确处理更复杂一些的循环引用情况(主要是在一些需要及时访问模块导出数据的情况下,譬如类继承(extends),静态引用等等)

    考虑下面的循环引用情况:

    import { C } from "./C.ts"
    
    export class A {
    // use C here
    }
    import { A } from "./A.ts"
    
    export class B extends A {
    // methods here
    }
    import { B } from "./B.ts"
    
    export class C extends B {
    // methods here
    }

    假设我们首先导入 A.ts,我们来分析下导入流程:

    A 模块尝试导入 C 模块
    由于 C 模块尚未导入,所以我们开始导入 C 模块
    C 模块尝试导入 B 模块
    由于 B 模块尚未导入,所以我们开始导入 B 模块
    B 模块尝试导入 A 模块
    由于 A 模块正在导入,所以程序直接返回当前导入结果
    B 模块继承 A 模块,尝试在当前(A 模块)导入结果中访问类型 A 的定义
    但是当前(A 模块)导入结果中并没有类型 A 的定义(因为当前 A 模块的导入还没有进行到 export class A)
    Ops,导入出错(找不到类型 A 的定义) …
    对于上面这种情况,其实有一个技巧可以解决上面的问题:在不需要及时访问模块导出数据的情况下,我们可以将模块的导入操作后置.

    就上面的例子来讲,我们可以这么修改代码:

    export class A {
    // use C here
    }
    
    // put import after export
    import { C } from "./C.ts"
    import { A } from "./A.ts"
    
    export class B extends A {
    // methods here
    }
    import { B } from "./B.ts"
    
    export class C extends B {
    // methods here
    }

    我们再来分析下上面代码的导入流程(仍然假设首先导入 A.ts):

    A 模块将类型 A 加入到 A 模块的导出数据中(export class A)
    A 模块尝试导入 C 模块
    由于 C 模块尚未导入,所以我们开始导入 C 模块
    C 模块尝试导入 B 模块
    由于 B 模块尚未导入,所以我们开始导入 B 模块
    B 模块尝试导入 A 模块
    由于 A 模块正在导入,所以程序直接返回当前导入结果
    类型 B 继承 类型 A ,尝试在当前(A 模块)导入结果中访问类型 A 的定义
    当前(A 模块)导入结果中存在类型 A 的定义, 类型 B 可以正常定义导出
    B 模块将类型 B 加入到 B 模块的导出数据中(export class B)
    B 模块导入完成,继续 C 模块的导入
    类型 C 继承 类型 B,尝试在当前(B 模块)导入结果中访问类型 B 的定义
    当前(B 模块)导入结果中存在类型 B 的定义, 类型 C 可以正常定义导出
    C 模块导入完成, 继续 A 模块的导入
    A 模块导入完成
    但是如果我们尝试首先导入 B 模块(B.ts)的话,仍然会遇到导入出错的问题:

    B 模块尝试导入 A 模块
    由于 A 模块尚未导入,所以我们开始导入 A 模块
    A 模块尝试导入 C 模块
    由于 C 模块尚未导入,所以我们开始导入 C 模块
    C 模块尝试导入 B 模块
    由于 B 模块正在导入,所以程序直接返回当前导入结果
    类型 C 继承 类型 B,尝试在当前(B 模块)导入结果中访问类型 B 的定义
    但是当前(B 模块)导入结果中并没有类型 B 的定义(因为当前 B 模块的导入还没有进行到 export class B)
    Ops,导入出错(找不到类型 B 的定义) …
    这种情况下,我们已经不能通过后置 import 来解决问题了(因为类型 B 和 类型 C 的定义导出都需要及时访问导入模块的导出数据),我们只能通过改变模块的导入顺序来规避导入出错的问题 …

  • 相关阅读:
    力扣516题、72题、1312题(最长回文子序列,编辑距离,构造回文串)
    力扣53题、1143题(最大子数组问题、最长公共子序列)
    力扣704题、34题(二分查找)
    力扣300题、354题(最长递增子序列,信封嵌套)
    力扣509题、70题(斐波那契数列、爬楼梯)
    力扣206题、92题、25题(反转链表)
    力扣234题(回文链表)
    力扣239题(单调队列)
    力扣496题、503题(单调栈)
    面试题简答题
  • 原文地址:https://www.cnblogs.com/lyonwu/p/14702331.html
Copyright © 2020-2023  润新知