參考
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關鍵詞,這樣外界就無法訪問這個類,這個類只在內部使用很好的封裝起來了。這是一個很好的用法。