【问题标题】:What's the difference between internal and external modules in TypeScript?TypeScript 中的内部模块和外部模块有什么区别?
【发布时间】:2012-10-02 05:39:09
【问题描述】:

我花了一些时间阅读 Typescript 语言规范,对 internalexternal 模块之间的区别感到有些困惑。以下是直接取自规范的描述:

内部模块(第 9.2.2 节)是其他模块(包括全局模块和外部模块)的本地或导出成员。内部模块使用 ModuleDeclarations 声明,指定它们的名称和主体。具有多个标识符的名称路径等价于一系列嵌套的内部模块声明。

外部模块(第 9.4 节)是使用外部模块名称引用的单独加载的代码体。外部模块被编写为单独的源文件,其中至少包含一个导入或导出声明。此外,可以在全局模块中使用 AmbientModuleDeclarations 声明外部模块,直接将外部模块名称指定为字符串文字。这将在第 0 节中进一步描述。

据我了解,我认为外部模块对应于打字稿文件,而没有包含仅导出一组类型和/或变量的模块定义。从另一个打字稿文件中,我可以使用import foo = module("foo");

foo.ts 中简单地导入一个外部模块

有人可以向我解释一下外部模块和内部模块的区别吗?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    规范的第 9.3 和 9.4 节更清楚地解释了这一点。我将在此处复制这些部分中给出的一些示例。

    外部模块

    假设以下代码在main.ts中。

    import log = module("log");
    log.message("hello");
    

    此文件引用外部模块 log,由 log.ts 导出的任何内容定义。

    export function message(s: string) { 
      console.log(s); 
    }
    

    请注意,log.ts 不会在任何地方使用 module 关键字。它只是用export 导出东西。

    内部模块

    这个文件有两个内部模块,X.Y.Z

    module A.B.C { 
      import XYZ = X.Y.Z; 
      export function ping(x: number) { 
        if (x > 0) XYZ.pong(x – 1); 
      }
    } 
    module X.Y.Z { 
      import ABC = A.B.C; 
      export function pong(x: number) { 
        if (x > 0) ABC.ping(x – 1); 
      } 
    }
    

    这些行为(大部分)类似于外部模块,但它们包含在一个文件中,您无需引用任何外部文件即可使用它们。定义它们时,它们必须包含在 module 块内。

    【讨论】:

    • 看起来外部模块是隐式模块,而内部模块是显式模块吗?
    • 如果明确表示“需要 module 关键字”,是的。
    • 是的,隐含的意思是“它是一个模块,因为内容在文件中”,而显式模块是显式的,因为它是使用关键字显式定义的。现在,所有这些都无助于理解环境模块的差异。
    【解决方案2】:

    根据 Anders 的介绍:http://channel9.msdn.com/posts/Anders-Hejlsberg-Introducing-TypeScript (34:40) 和 Typescript 文档,外部模块是基于顶级 AMD(异步模型定义)或 CommonJS 的模块。

    外部模块在某种意义上是有用的,它们隐藏了模块定义的内部语句,只显示与声明的变量相关的方法和参数。

    假设您有一个Main 类,其中定义了log 方法,并放置在transfer.js 文件中。 Main 类的内部方法仅在您导入源 js 文件顶部的 transfer.js 文件时可见:///<reference path="transfer.js"/>。这样编译器就消除了运行时对所有js文件的遍历。

    这是使用外部模块的巨大好处。另一个是当您尝试引用在正常自上而下的 javascript 流中定义晚于方法调用的外部方法或类时。使用外部模块,引用的类仅在方法调用时实例化。

    【讨论】:

    【解决方案3】:

    内部模块:

    1. 您可以在 typescriptp 文件中定义模块。
    2. 在模块中定义的所有变量都在模块范围内,并从全局范围中删除。
    3. 当您编译您的打字稿文件时,您的模块将转换为必要的嵌套变量以形成类似命名空间的对象。请注意,模块中定义的类使用 IIFE(立即调用函数表达式)巧妙地隔离。
    4. 下面的代码显示 MyClass 变量的作用域是 MyInternalModule 模块。它们无法在模块外部访问,这就是为什么代码的最后一行显示错误找不到名称 MyClass。
    5. 您可以使用 export 关键字访问模块外部的变量。
    6. 您还可以扩展内部模块,跨文件共享它们,并使用三斜杠语法引用它们。( /// )

    示例

    module MyInternalModule{  
        class MyClass{               //if We write export keyword before the MyClass then last line works fine
            constructor (
                public height: number, 
                public width: number) {
        }
        }                   
        //working properly
        var obj1 = new MyClass(10, 4);
    }
    
    // it wont work //Because the out of the scope
    var obj2 = new MyInternalModule.MyClass(10,4) //shows error: can not find name MyClass
    

    Typescript 的编译版本:

    var MyInternalModule;
    (function (MyInternalModule) {
        var MyClass = (function () {
            function MyClass(height, width) {
                this.height = height;
                this.width = width;
            }
            return MyClass;
        })();
        //working properly
        var obj1 = new MyClass(10, 4);
    })(MyInternalModule || (MyInternalModule = {}));
    

    外部模块:

    示例

    // bootstrapper.ts file
    
    // imports the greeter.ts file as the greeter module
    import gt = module('greeter');  
    export function run() {  
        var el = document.getElementById('content');
        var greeter = new gt.Greeter(el);
        greeter.start(); 
    }
    
    // greeter.ts file
    
    // exports the entire module
    export class Greeter {  
        start() {
             this.timerToken = setInterval(() => 
                 this.span.innerText = 
                 new Date().toUTCString(), 500);
        }
    }
    

    【讨论】:

      【解决方案4】:

      内部模块:namespacemodule 关键字

      声明

      可以使用namespacemodule 关键字声明内部模块。然后我们可以决定使用 export 关键字公开我们内部模块的哪一部分。

      // LivingThings.ts
      export namespace Animals {
          export class Dog { }
          export class Cat { }
      }
      export namespace Plants {
          export class Orchid { }
          export class Bamboo { }
      }
      
      // LivingThingsUser.ts
      import { Animals, Plants } from "./LivingThings"
      

      逻辑分组

      在 ES6 之前,Typescript 使用内部模块来封装接口、类、函数和变量,以支持一组相关功能并隐藏实现细节。这样我们可以防止变量泄漏到全局空间中。这有助于更好地组织代码并防止名称冲突。 现在建议使用外部模块(ES6 模块)来实现。

      内部模块现在用于环境命名空间声明。

      单个文件使用

      我们可以跨多个文件声明内部模块,并且可以使用--outFile 标志连接它们。然后,我们可以在 HTML 页面的 <script> 标记内使用该连接文件。这使我们能够在包含所有依赖项的客户端 Web 应用程序中以良好的方式构建我们的代码。


      外部模块:只需 exportimport 关键字

      声明

      外部模块也称为 ES6 模块。我们使用多个文件对相关功能进行分组,只需使用 export 关键字即可使所需对象公开可见。

      // Animals.ts
      export class Dog { }
      export class Cat { }
      
      // Plants.ts
      export class Orchid { }
      export class Bamboo { }
      
      // LivingThingsUser.ts
      import { Dog, Cat } from "./Animals"
      import { Orchid, Bamboo } from "./Plants"
      

      逻辑分组

      逻辑分组是通过使用单独的文件对相关功能进行分组来实现的。因此,外部模块也称为文件模块

      单个文件使用

      我们不使用<script> 标记加载客户端Web 应用程序的外部模块,因为浏览器可能会在下载这么多文件并同时呈现页面时变得迟缓。为此,我们使用 CommonJS、AMD、SystemJS 等模块加载器,使我们能够异步加载文件或将外部模块文件连接到单个优化文件中。

      对于服务器端,尤其是在 Node.js 中,强烈建议使用外部模块。


      命名法

      为了声明内部模块,打字稿团队建议使用namespace { } 而不是module { } 语法以避免与外部模块混淆。因为外部模块现在只是“模块”,而内部模块是“命名空间”。

      就是这样!

      【讨论】:

        猜你喜欢
        • 2014-04-05
        • 1970-01-01
        • 2012-11-30
        • 2017-10-17
        • 1970-01-01
        • 2014-05-11
        • 2015-11-25
        • 1970-01-01
        • 2015-08-11
        相关资源
        最近更新 更多