美文网首页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