Node.js中对通用模块的封装方法
在Node.js中对模块载入和执行进行了包装,使得模块文件中的变量在一个闭包中,不会污染全局变量,和他人冲突。
前端模块通常是我们开发人员为了避免和他人冲突才把模块代码放置在一个闭包中。
如何封装Node.js和前端通用的模块,我们可以参考Underscore.js实现,他就是一个Node.js和前端通用的功能函数模块,查看代码:
//CreateasafereferencetotheUnderscoreobjectforusebelow. var_=function(obj){ if(objinstanceof_)returnobj; if(!(thisinstanceof_))returnnew_(obj); this._wrapped=obj; }; //ExporttheUnderscoreobjectfor**Node.js**,with //backwards-compatibilityfortheold`require()`API.Ifwe'rein //thebrowser,add`_`asaglobalobjectviaastringidentifier, //forClosureCompiler"advanced"mode. if(typeofexports!=='undefined'){ if(typeofmodule!=='undefined'&&module.exports){ exports=module.exports=_; } exports._=_; }else{ root._=_; }
通过判断exports是否存在来决定将局部变量_赋值给exports,向后兼容旧的require()API,如果在浏览器中,通过一个字符串标识符“_”作为一个全局对象;完整的闭包如下:
(function(){ //Baselinesetup //-------------- //Establishtherootobject,`window`inthebrowser,or`exports`ontheserver. varroot=this; //CreateasafereferencetotheUnderscoreobjectforusebelow. var_=function(obj){ if(objinstanceof_)returnobj; if(!(thisinstanceof_))returnnew_(obj); this._wrapped=obj; }; //ExporttheUnderscoreobjectfor**Node.js**,with //backwards-compatibilityfortheold`require()`API.Ifwe'rein //thebrowser,add`_`asaglobalobjectviaastringidentifier, //forClosureCompiler"advanced"mode. if(typeofexports!=='undefined'){ if(typeofmodule!=='undefined'&&module.exports){ exports=module.exports=_; } exports._=_; }else{ root._=_; } }).call(this);
通过function定义构建了一个闭包,call(this)是将function在this对象下调用,以避免内部变量污染到全局作用域。浏览器中,this指向的是全局对象(window对象),将“_”变量赋在全局对象上“root._”,以供外部调用。
和Underscore.js类似的Lo-Dash,也是使用了类似的方案,只是兼容了AMD模块载入的兼容:
;(function(){ /**Usedasasafereferencefor`undefined`inpreES5environments*/ varundefined; /**UsedtodetermineifvaluesareofthelanguagetypeObject*/ varobjectTypes={ 'boolean':false, 'function':true, 'object':true, 'number':false, 'string':false, 'undefined':false }; /**Usedasareferencetotheglobalobject*/ varroot=(objectTypes[typeofwindow]&&window)||this; /**Detectfreevariable`exports`*/ varfreeExports=objectTypes[typeofexports]&&exports&&!exports.nodeType&&exports; /**Detectfreevariable`module`*/ varfreeModule=objectTypes[typeofmodule]&&module&&!module.nodeType&&module; /**DetectthepopularCommonJSextension`module.exports`*/ varmoduleExports=freeModule&&freeModule.exports===freeExports&&freeExports; /*--------------------------------------------------------------------------*/ //exposeLo-Dash var_=runInContext(); //someAMDbuildoptimizers,liker.js,checkforconditionpatternslikethefollowing: if(typeofdefine=='function'&&typeofdefine.amd=='object'&&define.amd){ //ExposeLo-DashtotheglobalobjectevenwhenanAMDloaderispresentin //caseLo-Dashwasinjectedbyathird-partyscriptandnotintendedtobe //loadedasamodule.TheglobalassignmentcanberevertedintheLo-Dash //modulebyits`noConflict()`method. root._=_; //defineasananonymousmoduleso,throughpathmapping,itcanbe //referencedasthe"underscore"module define(function(){ return_; }); } //checkfor`exports`after`define`incaseabuildoptimizeraddsan`exports`object elseif(freeExports&&freeModule){ //inNode.jsorRingoJS if(moduleExports){ (freeModule.exports=_)._=_; } //inNarwhalorRhino-require else{ freeExports._=_; } } else{ //inabrowserorRhino root._=_; } }.call(this));
再来看看Moment.js的封装闭包主要代码:
(function(undefined){ varmoment; //checkfornodeJS varhasModule=(typeofmodule!=='undefined'&&module.exports); /************************************ ExposingMoment ************************************/ functionmakeGlobal(deprecate){ varwarned=false,local_moment=moment; /*globalender:false*/ if(typeofender!=='undefined'){ return; } //here,`this`means`window`inthebrowser,or`global`ontheserver //add`moment`asaglobalobjectviaastringidentifier, //forClosureCompiler"advanced"mode if(deprecate){ this.moment=function(){ if(!warned&&console&&console.warn){ warned=true; console.warn( "AccessingMomentthroughtheglobalscopeis"+ "deprecated,andwillberemovedinanupcoming"+ "release."); } returnlocal_moment.apply(null,arguments); }; }else{ this['moment']=moment; } } //CommonJSmoduleisdefined if(hasModule){ module.exports=moment; makeGlobal(true); }elseif(typeofdefine==="function"&&define.amd){ define("moment",function(require,exports,module){ if(module.config().noGlobal!==true){ //IfuserprovidednoGlobal,heisawareofglobal makeGlobal(module.config().noGlobal===undefined); } returnmoment; }); }else{ makeGlobal(); } }).call(this);
从上面的几个例子可以看出,在封装Node.js和前端通用的模块时,可以使用以下逻辑:
if(typeofexports!=="undefined"){ exports.**=**; }else{ this.**=**; }
即,如果exports对象存在,则将局部变量装载在exports对象上,如果不存在,则装载在全局对象上。如果加上ADM规范的兼容性,那么多加一句判断:
if(typeofdefine==="function"&&define.amd){}