宝哥软件园

关于Javascript模块化和名称空间管理问题的说明

编辑:宝哥软件园 来源:互联网 时间:2021-10-21

【关于模块化以及为什么要模块化】我们先来说说为什么要模块化。其实与编码思想和代码管理的便利性有关(之所以没有提到命名空间污染的问题,是因为我相信考虑过模块化思想的程序员应该至少有一套自己的命名规则。在中小型网站中,名称空间污染的概率很小,但并不意味着不存在。这个问题将在后面讨论)。其实模块化的思想和面向对象的思想是一样的,只是我们口中所谓的“模块”是比所谓的“对象”更大的对象。我们通过良好的封装将专用于同一目的的功能功能组合起来,并确保它们具有良好的可重用性。我们可以把这种结合代码片段的想法称为面向对象的想法。有很多优点,比如易用性、通用性、可维护性、可读性、避免变量名污染等等。模块化无非是面向对象面向模块。我们有机地组合与同一个项目(模块)相关的功能包,并用一个共同的名称来管理它们。可以说是模块化的思想。所以相对于面向对象,我觉得在代码架构上实现模块化其实比面向对象更容易。与c#不同,java和其他强类型语言具有良好的模块化和命名空间机制。JavaScript不提供任何用于创建和管理模块的语言功能。正因为如此,当我们在编写js代码时,所谓的命名空间的使用有点太随意了(包括我自己)。例如,复制代码如下: varhong ru={ }//namespace(function(){ hong ru . class 1=function(){//todo }.hong ru . class 2=function(){//todo } }));如上所述,我们通常使用一个全局变量或全局对象作为我们的命名空间,这是如此简单,以至于它似乎被随意地赋予了如此重大的责任。但我们能说这不好吗?不,恰恰相反,觉得自己可以有这种编码习惯的同学,应该受到表扬。所以我们在做一些项目或者建设一些小规模的网站的时候,简单的这样做名字空间的工作其实就足够了,基本上不会有什么大的麻烦。但回归本质,如果你有代码洁癖,那就建一个大规模的网站,或者从一开始就以绝对优雅的态度和逻辑做代码架构。也许我们应该考虑一种更好的方法来注册和管理名称空间。在这方面,jQuery不如YUI、Mootool、EXT等。(虽然jq也有自己的模块化机制,但仍然不妨碍我们对它的热爱。毕竟侧重点不同,jq的优势在于它的选择器,否则就不会叫j-Query。所以,我们说jQuery适合中小网站也不是没有道理的。就像豆瓣的开源前端轻量级框架Do框架一样,也是建立在jQuery之上,封装了模块化管理的思想和文件同步加载的功能。[关于命名空间]好了,让我们回到主题。如上所述,简单地通过全局对象创建一个名称空间可以减少全局变量,避免变量名污染的问题。但是一旦网站规模变大或者项目多,在管理多个全局对象的命名空间时还是会出现问题。如果发生名称冲突,一个模块将覆盖另一个模块的属性,导致其中一个或两个模块无法正常工作。而且出现问题后,识别起来相当麻烦。因此,我们可能需要一组机制或工具来确定在创建名称空间时是否有重复的名称。另一方面,不同的模块,即不同的名称空间,并不是完全独立的。有时我们需要在不同的名称空间中建立相同的方法或属性,然后方法或属性的导入和导出就会成为问题。关于以上两个方面,我稍微思考了一下,做了一些测试,但还是有一些瑕疵。

今天又翻了一遍《犀牛书》,值得成为经典。上述问题已经很容易解决了。基于Rhino Book的解决方案和演示,我做了一些修改和简化。分享自己的理解。更重要的点如下:-测试每个子模块的可用性。由于我们的名字空间是一个对象,并且具有对象应该具有的层次关系,所以在测试名字空间的可用性时,有必要基于这样的层次关系进行判断和注册,这在注册子名字空间时尤为重要。比如我们新注册了一个名为Hongru的名字空间,然后我们需要注册另一个名为Hongru.me的名字空间,也就是我们的初衷是这个名字空间是Hongru的子名字空间,它们应该是有父子关系的。因此,在注册命名空间时,您需要拆分“.”并逐一做出相应的判断。因此,注册命名空间的代码大致如下:复制代码如下://create namespace-返回一个顶级命名空间模块。createnamespace=函数(名称,版本){if(!名称)引发新错误('需要名称');if (name.charAt(0)==' . '|| name.charAt(name.length-1)=' . '|| name.indexOf(' . ') !=-1)抛出新的错误(“非法名称”);var parts=name.split(' . ');var容器=Module.globalNamespacefor(var I=0;iparts.lengthI){ var part=parts[I];if(!container[part])container[part]={ };容器=容器[零件];} var命名空间=容器;if(命名空间。名称)引发新错误('模块''名称''已定义');命名空间。名称=名称;if(版本)命名空间。VERSION=版本;Module.modules[name]=命名空间;返回命名空间;};注意:上面的模块是我们注册和管理命名空间的通用模块。作为一个“基本模块”,它有一个模块的模块队列属性,用来存储我们新注册的命名空间。正是因为这个队列,我们可以很容易的判断出名字空间已经注册了:复制代码如下: var Module//检查模块-确保“模块”不存在,如果(!模块(模块类型!='对象' ||模块。名称))引发新错误(' NameSpace '模块'已经存在!');模块={ };模块。名称=“模块”;模块。VERSION=0.1模块。EXPORT=['require ',' import symbols '];模块。EXPORT_OK=['createNamespace ',' isDefined ',' modules ',' global namespace '];Module.globalNamespace=thisModule . modules={“Module”: Module };上面代码的最后一行是一个名称空间队列,所有新创建的名称空间都将放在其中。结合前面的代码,我们基本上可以管理我们的名称空间。至于“基本模块”,还有一些其他属性,如EXPORT,这将在后面讨论。下面是创建命名空间的测试演示拷贝代码。代码如下: module . create namespace(' hong ru ',0.1);//注册一个名为鸿儒的命名空间,版本0.1。如果不需要版本号,可以省略上面的第二个版本参数。我们可以在chrome调试器下看到我们新注册的名称空间

可以看到新注册的鸿儒命名空间已经生效:看看module的Module队列:

可以发现,新注册的鸿儒也被添加到模块的Modules队列中。您还发现模块中有几种方法,如require、isDefined和importSymbols。require(检测版本)和isDefined(检测命名空间时注册)这两种方法并不难,所以略作缩写:-版本和重名检测的重码代码如下://检查名称是否定义了module . is defined=function(name){返回module.modules中的名称;};//检查版本Module.require=函数(名称,版本){ if(!(Module.modules中的名称))引发新错误(“未定义模块“名称”);if(!版本)返回;var n=module . modules[name];if(!n.VERSION || n.VERSION)引发新错误('版本'版本'或更高版本是必需的');};以上两种方法很简单,相信大家都明白,一种是检查队列是否同名,另一种是检查版本是否达到了需要的版本。没什么特别的,就不细说了。更复杂的是名称空间之间属性或方法的相互导入。-导出名称空间中标签的属性或方法因为我们想要一个通用的名称空间注册和管理工具,所以在导入或导出标签时需要考虑可配置性,不能一次全部导入或导出。因此,我们在模块模板中有两个数组,EXPORT和EXPORT_OK,作为标记队列,用于存储我们允许导出的属性或方法。其中,EXPORT是公共标签队列,EXPORT_OK是我们可以自定义的标签队列。如果您认为不太清楚,您只能使用一个标记队列来存储您允许导出的标记属性或方法。对于标记队列,我们只对两个标记队列export和EXPORT_OK中的标记执行EXPORT操作。复制的代码如下://importmodule。import symbols=function(from){ if(type of form==' string ')from=module。模块[来自];var to=Module.globalNamespace//dafault var符号=[];var first symbol=1;if (arguments.length1类型的参数[1]=='object '参数[1]!=null) { to=参数[1];first symbol=2;} for(var a=first symbol;长度;a){ symbols . push(arguments[a]);} if(symbols . length==0){//默认导出列表if (from。EXPORT){ for(var I=0;ifrom。EXPORT.lengthi ) { var s=from。EXPORT[I];到[s]=从[s];}返回;} else if(!来自。EXPORT_OK) { //EXPORT数组EXPORT_OK数组均未定义为(var s in from){ to[s]=from[s];返回;} } } if (symbols.length 0) {允许varif (from。EXPORT ||表单。EXPORT_OK) {允许={ };if (from。EXPORT){ for(var I=0;无形的。EXPORT.lengthi ) {允许[从。EXPORT[I]]=true;} } if (from。EXPORT _ OK){ for(var I=0;无形的。导出_确定。长度;i ) {允许的[形式。EXPORT _ OK[I]]=true;} } } }//导入(var I=0;isymbols.lengthi ) { var s=符号[I];if(!(来自中的)引发新的错误(“未定义符号”);if(!允许!(s in allowed))引发新错误(s '不是公共的,不能导入');到[s]=形成[s];}}在这个方法中,第一个参数是导出源空间,第二个参数是导入目的地空间(可选,默认为定义的globalNamespace),下面的参数也是可选的,都是你要导出的具体属性或方法,默认为在队列中标记全部。下面是测试演示:复制代码如下: module . create namespace(' hong ru ');Module.createNamespace('me ',0.1);我。EXPORT=[' define ']me . define=function(){ this。NAME=' _ _ me} Module.importSymbols(me,hong ru);//将me命名空间中的标签导入到Hongru命名空间中即可看到测试结果:

本来定义在我名字空间下的方法定义()就被导入到鸿儒名字空间下了。当然,这里说的导入导出,其实只是收到,在我名字空间下依然能访问和使用定义()方法。好了,大概就说到这儿吧,这个演示也只是提供一种管理名字空间的思路,肯定有更加完善的方法,可以参考下YUI,EXT等框架。或者参考《JavaScript权威指南》 中模块和名字空间那节。最后贴下源码:复制代码代码如下:/*==模块和NameSpace工具-func==*作者: hongru.chen *日期: 2010-12-05 */var模块;//检查模块-确保"模块"不存在,如果(!模块(模块类型!='对象' ||模块。名称))引发新错误(“NameSpace”模块'已经存在!');模块={ };模块。名称="模块";模块版本=0.1模块EXPORT=['require ',' import symbols '];模块EXPORT_OK=['createNamespace ',' isDefined ',' modules ',' global namespace '];module . global namespace=this module。模块={“模块”:模块};//创建命名空间-返回一个顶级命名空间模块。create namespace=function(name,version) { if(!名称)引发新错误('需要名称');if (name.charAt(0)==' . '|| name.charAt(name.length-1)=' . '|| name.indexOf(' . ') !=-1)抛出新的错误("非法名称");var parts=name.split(' . ');定义变量容器=module . globalnamespace for(var I=0;ipart . LengTii){ var part=parts[I];if(!container[part])container[part]={ };容器=容器[零件];} var命名空间=容器;如果(命名空间。名称)引发新错误('模块''名称''已定义');命名空间。名称=名称;如果(版本)命名空间版本=版本;Module.modules[name]=命名空间;返回命名空间;};//检查名称是否已定义模块。已定义=函数(名称){在模块中返回名称;};//检查版本Module.require=函数(名称,版本){ if(!(模块中的名称))引发新错误("未定义模块"名称");if(!版本)返回;var n=模块。模块[名称];if(!n.VERSION || n.VERSION)引发新错误('版本'版本'或更高版本是必需的');};//导入模块模块。从=模块导入符号=函数(from){ if(形式类型==' string ')。模块[来自];var to=Module.globalNamespace//dafault var符号=[];var第一个符号=1;if(arguments . long 1类型的参数[1]=='对象'参数[1]!=null) { to=参数[1];第一个符号=2;} for(var a=第一个符号;长度;a){符号。推送(参数[a]);} if(符号。长度==0){//默认导出列表如果(从.EXPORT){ for(var I=0;红十字与红新月联会.EXPORT.lengthi ) { var s=from .EXPORT[I];到[s]=从[s];}返回;} else if(!来自EXPORT_OK) { //EXPORT数组导出_确定数组均未定义为(var s in from){ to[s]=from[s];返回;} } } if(symbols . length){ 0允许varif(来自.EXPORT ||表单导出_确定){ 0允许={ };如果(从.EXPORT){ for(var I=0;无形的export . lengthi){ 0允许[从EXPORT[I]]=true;} } if (from .EXPORT _ OK){ for(var I=0;无形的。导出_确定。长度;I){ 0允许的[形式EXPORT _ OK[I]]=true;} } } }//导入(var I=0;isymbols . lentigi){ var s=符号[一];if(!(来自中的)引发新的错误("未定义符号");if(!允许!(允许的)引发新错误(s '不是公共的,不能导入');到[s]=形成[s];} }

更多资讯
游戏推荐
更多+