JavaScript模块化解决方案模块化这个话题在ES6之前是不存在的,因此也被诟病为早期JavaScript开发全局污染和依赖管理混乱问题的根源。这种历史渊源和发展概况本文不再赘述,有兴趣的可以自行搜索JavaScript发展史。开门见山,让我们来看看常见的模块化解决方案有哪些,它们都有哪些内容。1.CommonjsCommonJS的一个模块是一个脚本文件,通过执行该文件来加载模块。CommonJS规范规定,在每个模块内部,模块变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载模块实际上是加载模块的module.exports属性。我们已经看到这样的模块引用:varmyModule=require('module');myModule.sayHello();这是因为我们在模块的属性上定义了模块的方法://module.jsmodule.exports.sayHello=function(){console.log('Hello');};//如果像这样写模块.exports=sayHello;//调用需要改成varsayHello=require('module');sayHello();require当脚本被加载一次时,整个脚本会被执行,然后生成一个对象在内存中(该模块可以多次加载,但会在第一次加载时运行,并缓存结果),结果如下:{id:'...',exports:{...},loaded:true,...}Node.js的模块机制实现参考了CommonJS标准。但是Node.js还做了一件事,就是为每个模块提供一个exports变量指向module.exports,相当于在每个模块的开头写了这样一行代码:varexports=module.exports;CommonJS模块的特点:所有代码都在模块作用域内运行,不会污染全局作用域。独立性是模块的一个重要特征,最好不要在模块内部直接与程序的其他部分进行交互。模块可以多次加载,但第一次加载时只会运行一次,然后缓存运行结果,后面加载时直接读取缓存的结果。要使模块再次工作,必须清除缓存。加载模块的顺序,按照它们在代码中出现的顺序。2、AMDCommonJS规范很好,但是不适合浏览器环境,所以有两种解决方案:AMD和CMD。AMD的全称是AsynchronousModuleDefinition,即异步模块定义。它异步加载模块,模块的加载不影响后面语句的运行。所有依赖该模块的语句都定义在一个回调函数中,回调函数只有在加载完成后才会运行。除了不同于CommonJS的同步加载方式外,AMD在模块的定义和引用上也有所不同。定义(id?,依赖关系?,工厂);AMD的模块引入是通过define方法定义的。defineAPI中:id:模块的名称,或者模块加载器请求的指定脚本的名称;dependencies:定义中的一个模块依赖于模块数组,默认是["require","exports","module"],举个例子更容易理解,当我们创建一个名为“alpha”的模块时,使用require、exports和“beta”模块需要写成如下(例1);factory:初始化模块要执行的函数或对象。如果是一个函数,它应该只执行一次。如果是一个对象,这个对象应该是模块的输出值;//示例1define("alpha",["require","exports","beta"],function(require,exports,beta){exports.verb=function(){returnbeta.verb();//或者returnrequire("beta").verb();}});如果模块定义中没有依赖,可以直接定义对象:define({add:function(x,y){returnx+y;}});在使用的时候,我们仍然使用require关键字,它包含两个参数,第一个数组是要加载的模块,第二个参数是回调函数:require([module],callback);例如:require(['math'],function(math){math.add(2,3);});3.CMDCMD的全称是CommonModuleDefinition,是Sea.js提倡的一种模块化方案的输出。在CMDdefine的输入参数中,虽然也支持id、deps、factory三个参数的形式,但是还是建议接受一个factory的输入参数,然后在输入的时候填写require、exports、module这三个参数参数执行。:define(function(require,exports,module){vara=require('./a');a.doSomething();varb=require('./b');b.doSomething();...})通过执行该构造方法,可以获得模块提供的接口。与AMD相比,主要有两点不同(来自Yubo的回答):对于依赖模块,AMD是提前执行的,CMD是延迟执行的。不过从RequireJS2.0开始,也可以改为延迟执行(根据不同的写法,不同的处理方式)。CMD提倡尽可能地懒惰。CMD提倡靠就近,AMD提倡靠前面。如果不清楚,那我们直接看上面的代码用AMD怎么写:define(['./a','./b'],function(a,b){a.doSomething();b.doSomething();...})4.UMDUMD,全称UniversalModuleDefinition,即通用模块规范。现在CommonJs和AMD风格一样流行,需要一个能够统一浏览器端和非浏览器端模块化解决方案的规范。直接来看看官方给的jQuery模块如何使用UMD定义的代码:(function(factory){if(typeofdefine==='function'&&define.amd){//AMD.Registerasananonymousmodule.define(['jquery'],factory);}elseif(typeofmodule==='object'&&module.exports){//Node/CommonJSmodule.exports=function(root,jQuery){if(jQuery===undefined){//require('jQuery')返回一个需要窗口的工厂//构建一个jQuery实例,我们标准化我们如何使用模块//需要这个模式但是提供的窗口是一个noop//如果它被定义(jquery是如何工作的)if(typeofwindow!=='undefined'){jQuery=require('jquery');}else{jQuery=require('jquery')(root);}}factory(jQuery);returnjQuery;};}else{//浏览器全局变量工厂(jQuery);}}(function($){$.fn.jqueryPlugin=function(){returntrue;};}));UMD的实现很简单:首先判断是否支持AMD(define是否存在),如果存在则使用AMD方式加载模块;然后判断是否支持Node.js模块格式(exports是否存在),存在则使用Node.js模块格式;如果前两个不存在,模块将暴露给全局(窗口或全局);5.ESModules当然,以上都是社区提供的解决方案。从历史上看,JavaScript直到ES6在语言标准级别实现它时才拥有模块系统。它的设计思想是尽量静态化,使得模块的依赖关系,以及输入输出的变量都可以在编译时确定。CommonJS和AMD模块都只能在运行时确定这些东西。例如,CommonJS模块是对象,必须在输入时查找对象属性。而ESModules并不是一个对象,而是一段代码,通过export命令明确指定输出。ESModules的模块化能力由导出和导入组成。export命令用于指定模块的对外接口,import命令用于导入其他模块提供的功能。我们可以这样定义一个模块://第一种方式exportvarfirstName='Michael';exportvarlastName='Jackson';exportvaryear=1958;//第二种方式varfirstName='Michael';varlastName='杰克逊';varyear=1958;export{firstName,lastName,year};然后像这样导入:除了以上两条命令外,还有一个exportdefault命令指定模块的默认输出(一个模块只能有一个默认输出)。如果使用exportdefault语法,导入时可以任意命名。由于exportdefault命令的本质是给default变量赋以下值,所以也可以在exportdefault后直接写一个值。当然还有很多参考方法:import{defaultasfoo}from'module';从“模块”导入foo;需要注意的是Modules会自动采用严格模式,import命令有提升作用,会提升到整个模块头,最先执行。延伸阅读JavaScript模块的循环加载资源搜索站点百科https://www.renrenfan.com.cn广州VI设计公司https://www.houdianzi.comwebpack打包输出配置问题。我们在开发一个JavaScript模块的时候,一定要经过打包的过程。在webpack配置中,通过指定output选项,我们可以告诉webpack如何输出bundle、asset等加载的内容。那么如何实现不同环境下的兼容构建呢?importthiswindowrequire输出中有一个名为libraryTarget的属性,用于指定如何公开模块的属性。你可以尝试像这样给变量或指定对象的属性赋值://加载后,将模块赋值给指定变量(默认值){libraryTarget:'var',...}//Assign到指定对象的一个??属性,比如`this`或`window`{libraryTarget:"this",//libraryTarget:"window",...}//同样,如果指定了commonjs,那么模块可以是分配给exports,这也意味着可以在CommonJS环境中使用:{libraryTarget:"commonjs",...}如果你需要一个更完整的模块化bundle来确保与每个模块系统的兼容性,你可以试试这个://内容是分配给module.exports对象,使用在CommonJS环境中{libraryTarget:'commonjs2',...}//作为AMD模块公开,通过特定属性引入{libraryTarget:'amd',...}//所有模块系统兼容万能,可以在CommonJS、AMD环境下使用,或者将模块导出到全局下的变量{libraryTarget:'umd',...}因此,如果只看输出内容,那么一个我的webpack生产环境配置可以这样写:module.exports={output:{//webpack输出结果相关的选项path:path.resolve(__dirname,"dist"),filename:'index.js',library:'hijiangtao',umdNamedDefine:true,libraryTarget:'umd',},}
