一、模块化简介随着前端js代码复杂度的增加,JavaScript模块化的概念被提出,前端社区不断推行前端模块化,直到es6将其标准化。1、什么是模块化?一个模块就是一个文件包:一堆模块包组成项目2.第一阶段:没有模块化JavaScript的初始功能只是验证表单,后面会添加一些动画,但是很多这样的js代码都在一个文件中就可以做到,所以我们只需要在html文件中添加一个script标签即可。后来随着前端的复杂度增加,为了提高项目代码的可读性和扩展性,我们的js文件也逐渐增多。不再是一个js文件可以解决,而是每个js文件作为一个模块。那么,此时引入js的方式是什么呢?可能是这样的:/script> 就是简单的把所有的js文件放在一起。但是这些文件的顺序不能错。比如需要先引入jquery,然后才能引入jquery插件,在其他文件中使用jquery。1.优点:与使用一个js文件相比,这种多个js文件实现最简单模块化的思路是一个进步。2.缺点:全局范围的污染。因为每个模块都是全局暴露的,简单的使用会造成全局变量命名冲突。当然我们也可以使用命名空间来解决。对于大型项目,js种类繁多,开发者必须手动解决模块和代码库之间的依赖关系,后期维护成本高。依赖关系不明显,不利于维护。比如main.js需要用到jquery,但是我们从上面的文件中是看不出来的。如果忘记了jquery,就会报错。3、第二阶段:CommonJS规范CommonJS是一种JavaScript模块化规范,最初是用在服务端的node中,前端webpack也原生支持CommonJS。按照这个规范,每个文件都是一个模块,里面定义的变量属于这个模块,不会暴露给外界,也就是说全局变量不会被污染。CommonJS的核心思想是使用require方法同步加载它所依赖的其他模块,然后使用exports或者module.exports导出需要暴露的接口。如下://a.jsvarx=5;varaddX=function(value){返回值+x;};module.exports.x=x;module.exports.addX=addX;这里的a.js是CommonJS规范的一个模块。这里的module代表这个模块,模块的exports属性就是对外暴露的接口,可以导出外部可以访问的变量,比如这里的x和addX。exports是对module.exports的引用。比如我们可以认为在一个模块的最前面有这样一段代码:exports=module.exports那么,我们不能直接给exports赋值,比如number,function等,那么我们可以在import这个模块其他模块:vara=require('./a.js');console.log(example.x);//5console.log(example.addX(1));//6这里的require会获取到a.js暴露的module.exports变量,然后就可以使用它暴露的x和addX了。1.优点:CommonJS规范率先在服务端完成了JavaScript的模块化,解决了依赖和全局变量污染的问题,这也是js运行在服务端的必要条件。2.缺点:这篇文章主要讲js在浏览器端的模块化。由于CommonJS是同步加载模块的,在服务器端,文件是保存在硬盘上的,所以同步加载没有问题,但是对于浏览器端来说,文件需要从服务器端请求的时候下载,同步加载是不适用,所以CommonJS不适用于浏览器端。四、第三阶段:之前提到的AMD规范:CommonJS规范加载模块是同步的,也就是说加载完成后才能进行后面的操作。AMD规范是一个异步加载的模块,允许指定回调函数。由于Node.js主要用于服务端编程,模块文件一般已经存在于本地硬盘上,所以加载速度更快,而且不需要考虑异步加载的方式,所以CommonJS规范更适用。但是如果是浏览器环境,则必须从服务器端加载模块,此时必须采用异步方式,所以浏览器端一般采用AMD规范。AMD规范的实现就是大名鼎鼎的require.js。在AMD标准中,定义了如下两个API:1.require([module],callback)2\.define(id,[depends],callback)通过define定义一个模块,然后使用require加载一个模块。此外,require还支持CommonJS模块导出方式。定义警报模块:define(function(){varalertName=function(str){alert("Iam"+str);}varalertAge=function(num){alert("Iam"+num+"岁");}return{alertName:alertName,alertAge:alertAge};});importmodule:require(['alert'],function(alert){alert.alertName('JohnZhu');alert.alertAge(21);});但是,在使用require.js的时候,我们必须提前加载所有的依赖才能使用,而不是在需要使用的时候才加载。1.优点:适合在浏览器环境下异步加载模块。可以并行加载多个模块。2.缺点:增加开发成本,不能按需加载,但必须提前加载所有依赖。5、第四阶段:CMD规范CMD规范由阿里的余波提出,实现js库为sea.js。它和requirejs很像,就是一个js文件就是一个模块,但是CMD的加载方式更好,是按需加载,而不是在模块开头加载所有的依赖。如下:define(function(require,exports,module){var$=require('jquery');varSpinning=require('./spinning');exports.doSomething=...module.exports=...})1。优点:同样实现了浏览器端的模块化加载。它可以按需加载并取决于最近的。2.缺点:依赖SPM封装,模块加载逻辑有偏差。其实这时候我们就可以看出AMD和CMD的区别了。前者是提前执行依赖模块,后者是延迟执行。前者提倡靠前,后者提倡靠近,即只在需要某个模块时才require。如下://AMDdefine(['./a','./b'],function(a,b){//依赖一定要写在开头a.doSomething()//b省略100行here.doSomething()...});//CMDdefine(function(require,exports,module){vara=require('./a')a.doSomething()//此处省略100行varb=require('./b')//依赖可以写在附近b.doSomething()//...});第六、第五阶段:ES6模块化之前的几种模块化方案都是前端社区自己实现的,才被大家认可和广泛使用,ES6的模块化方案才是真正的规范。在ES6中,我们可以使用import关键字导入模块,通过export关键字导出模块。功能比以前的解决方案更强大,我们也推荐它。但是,由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不支持的imports编译成目前广泛支持的require。虽然目前import和require区别不大,但是还是推荐使用es6,因为es6以后肯定是主流,代码迁移的成本还是很容易的。如:importstorefrom'../store/index'import{mapState,mapMutations,mapActions}from'vuex'importaxiosfrom'../assets/js/request'importutilfrom'../utils/js/util.js'exportdefault{created(){this.getClassify();这个.RESET_VALUE();console.log('创建',newDate().getTime());}
