美文网首页Typescripe我爱编程
TS 笔记十 namespace module

TS 笔记十 namespace module

作者: 合肥黑 | 来源:发表于2017-06-15 16:44 被阅读0次

    参考
    javascript全局变量污染会出现哪些问题?
    防止js全局变量污染方法总结-待续

    一、问题

    例如,你有两个.js。

    1.js:
    
    function f() {
        alert("f() in 1.js");
    }
    
    setTimeout(function() {
        f();
    }, 1000);
    2.js:
    
    function f() {
        alert("f() in 2.js");
    }
    
    setTimeout(function() {
        f();
    }, 2000);
    

    如果你在html中先载入1.js,再载入2.js,那么你就会看到两次"f() in 2.js"。因为后载入的2.js把f重新定义了。要比较实际的例子的话,可以想像1.js需要分割字符串,2.js需要分割数组,然后两个作者都写了个split函数,那肯定有一个模块要坏掉了

    二、解决办法
    1.定义全局变量命名空间

    只创建一个全局变量,并定义该变量为当前应用容器,把其他全局变量追加在该命名空间下

    var MY={};
            my.name={
                big_name:"zhangsan",
                small_name:"lisi"
            };
            my.work={
                school_work:"study",
                family_work:"we are"
           };
    
    2.利用匿名函数将脚本包裹起来
    (function(){
        var exp={};
        var name="aa";
        exp.method=function(){
            return name;
        };
        window.ex=exp;
    })();
    
    三、ts namespace module

    TypeScript中的module相当于ActionScript3中的Package
    命名空间:主要是为了区分不同人做的房子,以及系统的房子。你的房子可能是这个样子的他的是另一个样子,然后都是同一个名字,看起来没办法区分。就像A小区有一栋楼房叫6#,B小区恰好也有,我们要去B小区的6#怎么办?所以要去的话就要加个前缀,我要去B小区的6#,这个A小区和B小区就是命名空间了

    以下参考
    TypeScript新手入門
    TypeScript Modules(模块)
    在TS中【組織程式碼的方法】

    • 外部模組 - module
      模組之間是不同功能,利用import/export來互相引用彼此公開的功能

    • 內部模組 - namespace
      模組之間是相近的功能,使用namespace集中功能。

    1.moudle 外部模組
    • 使用 export (用法同ES6)
      將想要分享的變數、函式、類別及介面做 公開

    • 使用 import (用法同ES6)
      引用 不同檔案並設定公開的變數、函式、類別及介面

    MyExport.ts
        export class SomeType  { /* ... */ }
        export function someFn { /* ... */ }
    App.ts
        import { SomeType,somefn } form './Myexport';
        let x = new SomeType();
        let y = someFn();
    
    2.namespace 內部模組(命名空間)

    請先觀察以下寫法,有什麼缺點。

    interface Shape {
        area(h:number,w:number):number;
    }
    
    class Square implements Shape {
        area(h:number,w:number) {return h*w;}
    }
    
    class Triangle implements Shape { 
        area(h:number,w:number) {return (h*w) / 2;}
    }
    
    let s = new Square();
    console.log(s.area(10,5)); // 50
    let t = new Triangle();
    console.log(t.area(10,5)); // 25
    Shape、Square、Triangle 放在 global namespace
    

    放在global namespace的缺點是容易造成名稱衝突
    前例的寫法可修改成模組化,關鍵字使用namespace

    namespace Geometric { 
        const HALF = 0.5;
        
        export interface Shape {
            area(h:number,w:number):number;
        } 
        
        export class Square implements Shape {
            area(h:number,w:number) {return h*w;}
        }
    
        export class Triangle implements Shape { 
            area(h:number,w:number) { return (h*w)*HALF };
        }
    } //所以global namespace,只有Geometric這個物件
    
    let s = new Geometric.Square();
    console.log(s.area(10,5)); // 50
    let t = new Geometric.Triangle();
    console.log(t.area(10,5)); // 25
    

    我們希望介面跟類別是公開的,所以使用export公開。
    而變數HALF是實現的細節,就不必要使用export,
    因此變數HALF在模組外是不可見的。

    image.png

    可以觀察到編譯成ES3之後,模組是被包裝成立即函式,因此避免了全域環境汙染。

    隨著應用的擴展,我們希望將程式拆分成多個文件.使每個檔案的功能更單純,更方便維護。(單一職責原則)

    Shape.ts
    
    namespace Geometric {
        export interface Shape {
            area(h:number,w:number):number;
        } 
    }
    Square.ts
    
    /// <reference path="Shape.ts" />
    namespace Geometric {
        export class Square implements Shape {
            area(h:number,w:number) {return h*w;}
        }
    }
    Triangle.ts
    
    /// <reference path="Shape.ts" />
    namespace Geometric {
        export class Triangle implements Shape {
            area(h:number,w:number) {return h*w;}
        }
    }
    

    虽然每个文件是单独的,但他们都在为同一个模块贡献功能,并且在代码中定义他们的时候就会被调用。因为每个文件是相互依赖的,我们已经添加了"reference"标签来告诉编译器文件之间的关系。

    ps:关于reference,参考TypeScript 三斜线指令,/// <reference path="..." />指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。三斜线引用告诉编译器在编译过程中要引入的额外的文件。当使用--out或--outFile时,它也可以做为调整输出内容顺序的一种方法。 文件在输出文件内容中的位置与经过预处理后的输入顺序一致。

    App.ts
    
    /// <reference path="Shape.ts" />
    /// <reference path="Square.ts" />
    /// <reference path="Triangle.ts" />
    
    let s = new Geometric.Square();
    console.log(s.area(10,5)); // 50
    let t = new Geometric.Triangle();
    console.log(t.area(10,5)); // 25
    

    一旦有多个文件参与项目,我们得确保所需编译的代码是否都已加载,有两种方式可以实现。

    我们可以使用 -out 将所有的文件内容输出到一个单独的JavaScript文件中:
    tsc --out your.js Test.ts
    编译器会根据文件中的"reference"标签自动地将输出文件进行有序的排序,你也可以指定输出到单独的文件:

    tsc --out your.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
    

    或者我们也可以对每个文件进行单独的编译。如果产生多个js文件,我们就需要使用<script>标签用适当的顺序来加载文件,例如:

    MyTestPage.html (文件引用)
    
    <script src="Validation.js" type="text/javascript"></script />
    <script src="LettersOnlyValidator.js" type="text/javascript"></script />
    <script src="ZipCodeValidator.js" type="text/javascript"></script />
    <script src="Test.js" type="text/javascript"></script />
    

    別名

    當取用模組的path比較長時,可以使用 import q = x.y.z 的語法.給常用的模組起一個簡短的名稱

        namespace Shapes {
            export namespace Polygons {
                export class Square {}
                export class Triangle {}
            }
        }
        
        //沒有用別名之前
        var test1 = new Shapes.Polygons.Square();
        var test2 = new Shapes.Polygons.Triangle();
        
        //使用別名之後
        import pg = Shapes.Polygons;
        var sq = new pg.Square();
        var tri = new pg.Triangle();
    

    总结:
    模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。

    3.优先使用namespace

    以下参考TS1.5 以后,推荐全面使用namespace关键字代替module

    大体意思就是 TS1.5 以后,推荐全面使用namespace关键字代替module。因为JS里本身就有module的概念,而且已经是ES6标准里的关键字,各种加载框架比如CommonJS,AMD等也都有module的概念,但是TS里之前的module关键字与他们都不太相同。所以换了一个关键字加以区分,避免造成概念上的混淆。实际语法上,使用namespace等价于TS以前使用的module,然后推荐代码中不要再出现module关键字,这个关键字基本上变成了一个编译后和运行时里的概念,留给纯JS中使用。

    如果要用一句话解释TS里的namespace与JS里module的区别,那主要在于文件上:TS里的namespace是跨文件的,JS里的module是以文件为单位的,一个文件一个module。

    TS里的namespace主要是解决命名冲突的问题,会在全局生成一个对象,定义在namespace内部的类都要通过这个对象的属性访问,例如 egret.DisplayObject,egret就是namespace的对象,DisplayObject则是那个类名。因为是注册到全局的,所以跨文件也能正常使用,不同的文件能够读取其他文件注册在全局的命名空间内的信息,也可以注册自己的。namespace其实比较像其他面向对象编程语言里包名的概念。

    而JS里的module,主要是解决加载依赖关系的。跟文件绑定在一起,一个文件就是一个module。在一个文件中访问另一个文件必须要加载另一个文件。在NodeJS里是用CommonJS处理模块加载,因为是运行在本地,所以可以同步加载,写起来也比较方便。用到一个文件就require它一下,作为一个变量。而在Web端的RequireJS使用的是AMD处理模块加载,是异步的。其实就是把所有代码写在回调里,先去异步加载依赖的所有文件。

    所以可以简单的理解,namespace的概念等同于包名,module的概念等同于文件。

    namespace com.data{
    export class HashMap {
    }
    
    //使用
    import HashMap = com.data.HashMap;
    class ModuleManager {
    ...
    private moduleMap: HashMap;
        constructor() {
            this.moduleMap = new HashMap();
        }
    }
    

    最后,这个帖子讲得非常清楚关于TypeScript中的module和export关键词

    module大致的意思就是模块, 一个模块中有若干类,假如我写了两个类都叫 A 。那怎么区分呢,那么就使用这个module关键词将这两个类定义在不同模块就行了。module还有一个作用也是主要作用就是将一些不同特征的的类区分开。 比如 egret里面有几大模块,核心模块叫 egret , gui模块叫 egret.gui,RES模块就叫RES , dragonBones模块叫dragonBones。 这些模块就是按功能划分的,一个模块负责一些特定的功能。

    再比较一下as3中package关键词与module的不同。as3中一般一个类在哪个文件夹下,那这个类的package就是这个相对于src文件夹的名字,这样就不用担心不同文件夹下有名称相同的类而无法区分了。ts中module与类所在的文件夹无关,可能不同文件夹下的类都是一个module,一个文件夹下的类是不同module(这种情况最好不要出现)。 从某种角度来说,module的概念包括了package。你完全可以把某一个文件夹下的类定义的module定义成相对于src文件夹的名字就和as3的package是一样的。不过不推荐这种做法,这样会书写不便,引用每一个类都要加上module名前缀。

    在一个module下的不同类之间的相互调用不需要加模块名。比如 egret这个模块中有很多类但是在egret的源码中你几乎看不到egret.XXX这样的调用,因为他们都是在一个模块下。 同理假如你写了个类module是egret,那这个类调用egret里面的类也不需要加egret前缀了。但是不建议这样做,因为模块的核心用法就是定义一组相同特征的类。

    子模块定义。我们可以查看egret中GUI的源码,发现GUI中的类module名都是egret.gui。这个gui就是egret的子模块了。 在子模块中调用父模块的类也是不需要加前缀的。比如egret.gui中的类调用egret中的类是不需要加egret前缀的。 在父模块中调用子模块只需要加上相对于父模块的模块名就行了。比如egret中的类调用egret.gui中的类使用gui.XXX。

    关于export的用法。 在使用module时定义一个类需要在前面加上export关键词。表示在这个模块中导入了这个类(在默认模块下不需要加export)。也可以不加但是不加的话这个类是无法在这个文件外部访问的,相当与内部类。另外export还可以用于function。这些用法的一个典型的例子就是RES模块中, 我们通常会使用RES.getRes(XXX)来获取资源,好像这是一个叫RES类的静态方法,其实不然。搜索下发现根本就没有RES这个类,RES是模块名,getRes是RES模块下的一个方法。代码在Resource.ts文件中,如下:

        export function getRes(key:string):any{
            return instance.getRes(key);
        }
    

    所以export也可以用于导入方法。同理 egret.setTimeout这类的方法都是使用export导入的方法 。再来看上述例子中的 instance 实际上是Resource这个类的一个实例,只不过这个类是一个内部类。可以看到在Resource.ts是这样定义的

    class Resource extends egret.EventDispatcher{
    }
    

    这里没用使用export关键词,这样外界就无法访问这个类,这个类只在内部使用很好的封装起来了。这是一个很好的用法。

    4.

    typescript已经有模块系统了,为什么还需要namespace? - Trotyl Yu的回答 - 知乎

    相关文章

      网友评论

        本文标题:TS 笔记十 namespace module

        本文链接:https://www.haomeiwen.com/subject/obfmqxtx.html