参考
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在模組外是不可見的。
可以觀察到編譯成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关键词,这样外界就无法访问这个类,这个类只在内部使用很好的封装起来了。这是一个很好的用法。