在ES6出现以前,JS语言自己并无提供模块化能力,这为开发带来了一些问题,其中最重要的两个问题应当是全局污染和依赖管理混乱。html
// file a.js var name = 'aaa'; var sayName = function() { console.log(name); };
<!-- file index.html --> <script src='xxx/xxx/a.js'></script> <script> sayName(); // 'aaa' // code... var name = 'bbb'; sayName(); // 'bbb' </script>
上面的代码中,咱们两次调用a.js所提供的sayName函数输出了不一样结果,很明显这是由于两个文件都对变量name进行了赋值,所以相互之间形成了影响。固然咱们能够在编写代码时注意不要定义已存在的变量名,可是当一个页面引用了10几个几百行的文件时,记住全部已经定义过的变量显然不太现实。前端
// file a.js var name = getName(); var sayName = function() { console.log(name) };
// file b.js var getName = function() { return 'timo'; };
<script src='xxx/xxx/b.js'></script> <script src='xxx/xxx/a.js'></script> <script> sayName(); // 'timo' </script>
<script src='xxx/xxx/a.js'></script> <script src='xxx/xxx/b.js'></script> // Uncaught ReferenceError: getName is not defined
上面的代码说明,多个文件有依赖关系时,咱们须要确保其引入的顺序,从而保证运行某个文件时,其依赖已经提早加载,能够想象,面对越大型的项目,咱们须要处理的依赖关系也就越多,这既麻烦又容易出错。jquery
而为了解决这些,社区中出现了许多为JS语言提供模块化能力的规范,借助这些规范,能让咱们的开发更加方便安全。数组
CommonJS是由社区提出的模块化方案中的一种,Node.js遵循了这套方案。浏览器
// file a.js var obj = { sayHi: function() { console.log('I am timo'); }; }; module.exports obj;
// file b.js var Obj = require('xxx/xxx/a.js'); Obj.sayHi(); // 'I am timo'
上面的代码中,文件a.js是模块的提供方,文件b.js是模块调用方。安全
CommonJS的规范,简单来讲就是上面4条,能够对照基本写法中的例子理解一下,在实际实现中,Node.js虽然遵循CommonJS规范,可是仍然对其进行了一些调整。模块化
AMD是模块化规范中的一种,RequireJS遵循了这套规范。函数
// file a.js define('module', ['m', './xxx/n.js'], function() { // code... })
上面的代码中,文件a.js向外导出了模块;网站
AMD中,暴露模块使用define函数ui
define(moduleName, [], callback);
如上面代码,define函数共有三个参数
ADM的特色是依赖前置,这是ADM规范与接下来要介绍的CMD规范最大的不一样,依赖前置是指:在运行当前加载模块回调前,会首先将全部依赖包加载完毕,也是就是define函数的第二个参数中指定的依赖包。
define(function(require, exports, module) { var a = require('./a') a.doSomething(); // code... var b = require('./b') // code... })
上面代码是CMD规范导出模块的基本写法;
从写法能够看出,CMD的写法和AMD很是像,其主要区别是对于依赖加载时机的不一样,上面已经说过,AMD是依赖前置,而CMD规范推崇就近原则,简单说就是在模块运行前并不加载依赖,模块运行过程当中,当须要某个依赖时,再去进行加载。
CommonJS、AMD、CMD并行的状态下,就须要一种方案可以兼容他们,这样咱们在开发时,就不须要再去考虑依赖模块所遵循的规范了,而UMD的出现就是为了解决这个问题。
(function (root, factory) { if (typeof define === 'function' && define.amd) { //AMD define(['jquery'], factory); } else if (typeof exports === 'object') { //Node, CommonJS之类的 module.exports = factory(require('jquery')); } else { //浏览器全局变量(root 即 window) root.returnExports = factory(root.jQuery); } }(this, function ($) { //方法 function myFunc(){}; //暴露公共方法 return myFunc; }));
上面的代码是UMD的基本写法,从代码就能够看出,其可以同时支持CommonJS规范和AMD规范。
上面分别介绍了CommonJS、AMD、CMD和UMD,他们都是社区对于JS实现模块化的贡献,这个规范其产生的根本缘由,都是JS语言自身没有模块化能力,而目前,在JS最新的语言规范ES6中,已经为JS增长了模块化能力,而JS自身的模块化方案,彻底可以替代目前社区提出的各种规范,且可以作到浏览器端和Node端通用。
ES6中的模块化能力由两个命令构成:export和import,export命令用于规定模块的对外接口,import命令用于输入其余模块提供的功能。
ES6中一个文件就是一个模块,模块内部的变量/函数等外部是没法访问的,若是但愿将内部的函数/变量等对外暴露,供其余模块进行使用,就须要经过export命令进行导出
// file a.js export let a = 1; export let b = 2; export let c = 3;
// file b.js let a = 1; let b = 2; let c = 3; export {a, b, c}
// file c.js export let add = (a, b) => { return a + b; };
上面三个文件的代码,都是经过export命令导出模块内容的示例,其中a.js文件和b.js文件都是导出模块中的变量,做用彻底一致但写法不一样,通常咱们更推荐b.js文件中的写法,缘由是这种写法可以在文件最底部清楚地知道当前模块都导出了哪些变量。
模块经过export命令导出变量/函数等,是为了让其余模块可以导入去使用,在ES6中,文件导入其余模块是经过import命令进行的
// file d.js import {a, b, c} from './a.js';
上面的代码中,咱们引入了a.js文件中的变量a、b、c,import在引入其余模块内的函数/变量时,必须与原模块所暴露出来的函数名/变量名一一对应。
同时,import命令引入的值是只读的,若是尝试对其进行修改,则会报错
import {a} d from './a.js'; a = 2; // Syntax Error : 'a' is read-only;
从上面import的介绍能够看到,当须要引入其余模块时,须要知道此模块暴露出的变量名/函数名才能够,这显然有些麻烦,所以ES6还提供了一个import default命令
// file a.js let add = (a, b) => { return a+b }; export default add;
// file b.js import Add from './a.js'; Add(1, 2); // 3
上面的代码中,a.js经过export default命令导出了add函数,在b.js文件中引入时,能够随意指定其名称
export default命令是默认导出的意思,既然是默认导出,显然只能有一个,所以每一个模块只能执行一次export default命令,其本质是导出了一个名为default的变量或函数。
最后再来总结一下, 首先在以前的JS语言中没有模块化能力,而随着网站功能的复杂,开发愈来愈不方便,所以社区中出现了一批为JS提供模块化能力的方案,其中比较主流的就是咱们介绍过的CommonJS、AMD、CMD和UMD等,而以后发布的ES6语言标准,从JS语言自身提供了模块化能力,所以,随着ES6的逐步普及,ES6 module应该会逐步取代目前的各种社区规范,但不能否认,在没有ES6的日子里,这些社区规范给前端人员提供了巨大的方便,并推进了JS的发展。