欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于Javascript模塊化和命名空間管理的問題說明

 更新時間:2010年12月06日 21:11:31   作者:  
最近閑下來的時候,稍微想了想這個問題。關(guān)于Javascript模塊化和命名空間管理
【關(guān)于模塊化以及為什么要模塊化】

先說說我們?yōu)槭裁匆K化吧。其實這還是和編碼思想和代碼管理的便利度相關(guān)(沒有提及名字空間污染的問題是因為我相信已經(jīng)考慮到模塊化思想的編碼者應(yīng)該至少有了一套自己的命名法則,在中小型的站點中,名字空間污染的概率已經(jīng)很小了,但也不代表不存在,后面會說這個問題)。
其實模塊化思想還是和面向?qū)ο蟮乃枷肴绯鲆晦H,只不過可能我們口中所謂的“模塊”是比所謂的“對象”更大的對象而已。我們把致力完成同一個目的的功能函數(shù)通過良好的封裝組合起來,并且保證其良好的復(fù)用性,我們大概可以把這樣一個組合代碼片段的思想稱為面向?qū)ο蟮乃枷?。這樣做的好處有很多,比如:易用性,通用性,可維護性,可閱讀性,規(guī)避變量名污染等等。
而模塊化無非就是在面向?qū)ο笊系拿嫦蚰K而已,我們把和同一個項目(模塊)相關(guān)的功能封裝有機的組合起來,通過一個共同的名字來管理。就大概可以說是模塊化的思想。所以,相比面向?qū)ο蠖缘脑?,我覺得在代碼架構(gòu)上貫徹模塊化的思想其實比面向?qū)ο蟮呢瀼剡€更為容易一些。
不像c#,java等這種本身就擁有良好模塊化和命名空間機制的強類型語言。JavaScript并沒有為創(chuàng)建和管理模塊而提供任何語言功能。正因為這樣,我們在做js的編碼的某些時候,對于所謂的命名空間(namespace)的使用會顯得有些過于隨便(包括我自己)。比如 :
復(fù)制代碼 代碼如下:

var Hongru = {} // namespace

(function(){
Hongru.Class1 = function () {
//TODO
}
...
Hongru.Class2 = function () {
//TODO
}
})();


如上,我們通常用一個全局變量或者全局對象就作為我們的namespace,如此簡單,甚至顯得有些隨便的委以它這么重大的責任。但是我們能說這樣做不好嗎?不能,反而是覺得能有這種編碼習慣的同學應(yīng)該都值得表揚。。。


所以,我們在做一些項目的時候或者建一些規(guī)模不大的網(wǎng)站時,簡單的用這種方式來做namespace的工作其實也夠了,基本不會出什么大亂子。但是回歸本質(zhì),如果是有代碼潔癖或者是建立一個大規(guī)模的網(wǎng)站,抑或一開始就抱著絕對優(yōu)雅的態(tài)度和邏輯來做代碼架構(gòu)的話。或許我們該考慮更好一些的namespace的注冊和管理方式。
在這個方面,jQuery相比于YUI,Mootool,EXT等,就顯得稍遜一籌,(雖然jq也有自己的一套模塊化機制),但這依然不妨礙我們對它的喜愛,畢竟側(cè)重點不同,jq強是強在它的選擇器,否則他也不會叫j-Query了。
所以我們說jQuery比較適合中小型的網(wǎng)站也不無道理。就像豆瓣的開源的前端輕量級框架Do框架一樣,也是建立在jQuery上,封裝了一層模塊化管理的思想和文件同步載入的功能。

【關(guān)于namespace】

好了,我們回歸正題,如上的方式,簡單的通過全局對象來做namespace已經(jīng)能夠很好的減少全局變量,規(guī)避變量名污染的問題,但是一旦網(wǎng)站規(guī)模變大,或者項目很多的時候,管理多個全局對象的名字空間依然會有問題。如果不巧發(fā)生了名字沖突,一個模塊就會覆蓋掉另一個模塊的屬性,導致其一或者兩者都不能正常工作。而且出現(xiàn)問題之后,要去甄別也挺麻煩。所以我們可能需要一套機制或者工具,能在創(chuàng)建namespace的時候就能判斷是否已有重名。

另一方面,不同模塊,亦即不同namespace之間其實也不能完全獨立,有時候我們也需要在不同名字空間下建立相同的方法或?qū)傩?,這時方法或?qū)傩缘膶牒蛯С鲆矔莻€問題。

就以上兩個方面,我稍微想了想,做了些測試,但依然有些紕漏。今天又重新翻了一下“犀牛書”,不愧是經(jīng)典,上面的問題,它輕而易舉就解決了。基于“犀牛書”的解決方案和demo,我稍微做了些修改和簡化。把自己的理解大概分享出來。比較重要的有下面幾個點:

--測試每一個子模塊的可用性

由于我們的名字空間是一個對象,擁有對象應(yīng)該有的層級關(guān)系,所以在檢測名字空間的可用性時,需要基于這樣的層級關(guān)系去判斷和注冊,這在注冊一個子名字空間(sub-namespace)時尤為重要。比如我們新注冊了一個名字空間為Hongru,然后我們需要再注冊一個名字空間為Hongru.me,亦即我們的本意就是me這個namespace是Hongru的sub-namespace,他們應(yīng)該擁有父子的關(guān)系。所以,在注冊namespace的時候需要通過‘.'來split,并且進行逐一對應(yīng)的判斷。所以,注冊一個名字空間的代碼大概如下:

復(fù)制代碼 代碼如下:

// create namespace --> return a top namespace
Module.createNamespace = function (name, version) {
if (!name) throw new Error('name required');
if (name.charAt(0) == '.' || name.charAt(name.length-1) == '.' || name.indexOf('..') != -1) throw new Error('illegal name');

var parts = name.split('.');

var container = Module.globalNamespace;
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (!container[part]) container[part] = {};
container = container[part];
}

var namespace = container;
if (namespace.NAME) throw new Error('module "'+name+'" is already defined');
namespace.NAME = name;
if (version) namespace.VERSION = version;

Module.modules[name] = namespace;
return namespace;
};

注:上面的Module是我們來注冊和管理namespace的一個通用Module,它本身作為一個“基模塊”,擁有一個modules的模塊隊列屬性,用來存儲我們新注冊的名字空間,正因為有了這個隊列,我們才能方便的判斷namespace時候已經(jīng)被注冊:


復(fù)制代碼 代碼如下:

var Module;
//check Module --> make sure 'Module' is not existed
if (!!Module && (typeof Module != 'object' || Module.NAME)) throw new Error("NameSpace 'Module' already Exists!");

Module = {};

Module.NAME = 'Module';
Module.VERSION = 0.1;

Module.EXPORT = ['require',
'importSymbols'];

Module.EXPORT_OK = ['createNamespace',
'isDefined',
'modules',
'globalNamespace'];

Module.globalNamespace = this;

Module.modules = {'Module': Module};

上面代碼最后一行就是一個namespace隊列,所有新建的namespace都會放到里面去。結(jié)合先前的一段代碼,基本就能很好的管理我們的名字空間了,至于Module這個“基模塊”還有些EXPORT等別的屬性,等會會接著說。下面是一個創(chuàng)建名字空間的測試demo

復(fù)制代碼 代碼如下:

Module.createNamespace('Hongru', 0.1);//注冊一個名為Hongru的namespace,版本為0.1

上面第二個版本參數(shù)也可以不用,如果你不需要版本號的話。在chrome-debugger下可以看到我們新注冊的名字空間


可以看到新注冊的Hongru命名空間已經(jīng)生效:再看看Module的模塊隊列:

可以發(fā)現(xiàn),新注冊的Hongru也添進了Module的modules隊列里。大家也發(fā)現(xiàn)了,Module里還有require,isDefined,importSymbols幾個方法。
由于require(檢測版本),isDefined(檢測namespace時候已經(jīng)注冊)這兩個方法并不難,就稍微簡略點:

--版本和重名檢測 


復(fù)制代碼 代碼如下:

// check name is defined or not
Module.isDefined = function (name) {
return name in Module.modules;
};
// check version
Module.require = function (name, version) {
if (!(name in Module.modules)) throw new Error('Module '+name+' is not defined');
if (!version) return;
var n = Module.modules[name];
if (!n.VERSION || n.VERSION < version) throw new Error('version '+version+' or greater is required');
};

上面兩個方法都很簡單,相信大家都明白,一個是隊列檢測是否重名,一個檢測版本是否達到所需的版本。也沒有什么特別的地方,就不細講了,稍微復(fù)雜一點的是名字空間之間的屬性或方法的相互導入的問題。
--名字空間中標記的屬性或方法的導出
由于我們要的是一個通用的名字空間注冊和管理的tool,所以在做標記導入或?qū)С龅臅r候需要考慮到可配置性,不能一股腦全部導入或?qū)С?。所以就有了我們看到的Module模板中的EXPORT和EXPORT_OK兩個Array作為存貯我們允許導出的屬性或方法的標記隊列。其中EXPORT為public的標記隊列,EXPORT_OK為我們可以自定義的標記隊列,如果你覺得不要分這么清楚,也可以只用一個標記隊列,用來存放你允許導出的標記屬性或方法。
有了標記隊列,我們做的導出操作就只針對EXPORT和EXPORT_OK兩個標記隊列中的標記。
復(fù)制代碼 代碼如下:

// import module
Module.importSymbols = function (from) {
if (typeof form == 'string') from = Module.modules[from];
var to = Module.globalNamespace; //dafault
var symbols = [];
var firstsymbol = 1;
if (arguments.length>1 && typeof arguments[1] == 'object' && arguments[1] != null) {
to = arguments[1];
firstsymbol = 2;
}
for (var a=firstsymbol; a<arguments.length; a++) {
symbols.push(arguments[a]);
}
if (symbols.length == 0) {
//default export list
if (from.EXPORT) {
for (var i=0; i<from.EXPORT.length; i++) {
var s = from.EXPORT[i];
to[s] = from[s];
}
return;
} else if (!from.EXPORT_OK) {
// EXPORT array && EXPORT_OK array both undefined
for (var s in from) {
to[s] = from[s];
return;
}
}
}
if (symbols.length > 0) {
var allowed;
if (from.EXPORT || form.EXPORT_OK) {
allowed = {};
if (from.EXPORT) {
for (var i=0; i<form.EXPORT.length; i++) {
allowed[from.EXPORT[i]] = true;
}
}
if (from.EXPORT_OK) {
for (var i=0; i<form.EXPORT_OK.length; i++) {
allowed[form.EXPORT_OK[i]] = true;
}
}
}
}
//import the symbols
for (var i=0; i<symbols.length; i++) {
var s = symbols[i];
if (!(s in from)) throw new Error('symbol '+s+' is not defined');
if (!!allowed && !(s in allowed)) throw new Error(s+' is not public, cannot be imported');
to[s] = form[s];
}
}

這個方法中第一個參數(shù)為導出源空間,第二個參數(shù)為導入目的空間(可選,默認是定義的globalNamespace),后面的參數(shù)也是可選,為你想導出的具體屬性或方法,默認是標記隊列里的全部。
下面是測試demo:
復(fù)制代碼 代碼如下:

Module.createNamespace('Hongru');
Module.createNamespace('me', 0.1);
me.EXPORT = ['define']
me.define = function () {
this.NAME = '__me';
}
Module.importSymbols(me, Hongru);//把me名字空間下的標記導入到Hongru名字空間下

可以看到測試結(jié)果:
 

 

 本來定義在me名字空間下的方法define()就被導入到Hongru名字空間下了。當然,這里說的導入導出,其實只是copy,在me名字空間下依然能訪問和使用define()方法。

好了,大概就說到這兒吧,這個demo也只是提供一種管理名字空間的思路,肯定有更加完善的方法,可以參考下YUI,EXT等框架?;蛘邊⒖肌禞avaScript權(quán)威指南》中模塊和名字空間那節(jié)。

最后貼下源碼:

復(fù)制代碼 代碼如下:

/* == Module and NameSpace tool-func ==
* author : hongru.chen
* date : 2010-12-05
*/
var Module;
//check Module --> make sure 'Module' is not existed
if (!!Module && (typeof Module != 'object' || Module.NAME)) throw new Error("NameSpace 'Module' already Exists!");
Module = {};
Module.NAME = 'Module';
Module.VERSION = 0.1;
Module.EXPORT = ['require',
'importSymbols'];
Module.EXPORT_OK = ['createNamespace',
'isDefined',
'modules',
'globalNamespace'];
Module.globalNamespace = this;
Module.modules = {'Module': Module};
// create namespace --> return a top namespace
Module.createNamespace = function (name, version) {
if (!name) throw new Error('name required');
if (name.charAt(0) == '.' || name.charAt(name.length-1) == '.' || name.indexOf('..') != -1) throw new Error('illegal name');
var parts = name.split('.');
var container = Module.globalNamespace;
for (var i=0; i<parts.length; i++) {
var part = parts[i];
if (!container[part]) container[part] = {};
container = container[part];
}
var namespace = container;
if (namespace.NAME) throw new Error('module "'+name+'" is already defined');
namespace.NAME = name;
if (version) namespace.VERSION = version;
Module.modules[name] = namespace;
return namespace;
};
// check name is defined or not
Module.isDefined = function (name) {
return name in Module.modules;
};
// check version
Module.require = function (name, version) {
if (!(name in Module.modules)) throw new Error('Module '+name+' is not defined');
if (!version) return;
var n = Module.modules[name];
if (!n.VERSION || n.VERSION < version) throw new Error('version '+version+' or greater is required');
};
// import module
Module.importSymbols = function (from) {
if (typeof form == 'string') from = Module.modules[from];
var to = Module.globalNamespace; //dafault
var symbols = [];
var firstsymbol = 1;
if (arguments.length>1 && typeof arguments[1] == 'object' && arguments[1] != null) {
to = arguments[1];
firstsymbol = 2;
}
for (var a=firstsymbol; a<arguments.length; a++) {
symbols.push(arguments[a]);
}
if (symbols.length == 0) {
//default export list
if (from.EXPORT) {
for (var i=0; i<from.EXPORT.length; i++) {
var s = from.EXPORT[i];
to[s] = from[s];
}
return;
} else if (!from.EXPORT_OK) {
// EXPORT array && EXPORT_OK array both undefined
for (var s in from) {
to[s] = from[s];
return;
}
}
}
if (symbols.length > 0) {
var allowed;
if (from.EXPORT || form.EXPORT_OK) {
allowed = {};
if (from.EXPORT) {
for (var i=0; i<form.EXPORT.length; i++) {
allowed[from.EXPORT[i]] = true;
}
}
if (from.EXPORT_OK) {
for (var i=0; i<form.EXPORT_OK.length; i++) {
allowed[form.EXPORT_OK[i]] = true;
}
}
}
}
//import the symbols
for (var i=0; i<symbols.length; i++) {
var s = symbols[i];
if (!(s in from)) throw new Error('symbol '+s+' is not defined');
if (!!allowed && !(s in allowed)) throw new Error(s+' is not public, cannot be imported');
to[s] = form[s];
}
}

相關(guān)文章

最新評論