本篇分为两个部分
第一部分:总结了ES6出现以前,在当时现有的运行环境中,实现"模块"的方式;
第二部分:总结了ES6出现后,module成为ES6标准,客户端实现模块化的解决方案;javascript
Javascript社区作了不少努力,在当时现有的运行环境中,实现了“模块”的效果前端
原始写法:java
模块就是实现特定功能的一组方法。
只要把不一样的函数(以及记录状态的变量)简单地放在一块儿,就算是一个模块。node
1 function m1(){ 2 //... 3 } 4 function m2(){ 5 //... 6 }
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就好了。
这种作法的缺点很明显:"污染"了全局变量,没法保证不与其余模块发生变量名冲突,并且模块成员之间看不出直接关系。jquery
对象写法:程序员
为了解决上面的缺点,能够把模块写成一个对象,全部的模块成员都放到这个对象里面es6
1 var module1 = new Object({ 2 _count : 0, 3 m1 : function (){ 4 //... 5 }, 6 m2 : function (){ 7 //... 8 } 9 });
上面的函数m1()和m2(),都封装在 module1 对象里。使用的时候,就是调用这个对象的属性编程
1 module1.m1();
这样的写法会暴露全部模块成员,内部状态能够被外部改写。好比,外部代码能够直接改变内部计数器的值。数组
1 module._count = 1;
当即执行函数写法:浏览器
使用"当即执行函数"(Immediately-Invoked Function Expression,IIFE),能够达到不暴露私有成员的目的
1 var module = (function() { 2 var _count = 0; 3 var m1 = function() { 4 alert(_count) 5 } 6 var m2 = function() { 7 alert(_count + 1) 8 } 9 return { 10 m1: m1, 11 m2: m2 12 } 13 })();
使用上面的写法,外部代码没法读取内部的_count变量。console.info(module._count); //undefined
module就是Javascript模块的基本写法。
在es6之前,尚未提出一套官方的规范,从社区和框架推广程度而言, 通行的javascript模块规范有两种:CommonJS 和 AMD
1.1 CommonJS 规范:
2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志着"Javascript模块化编程"正式诞生。前端(客户端)的复杂程度有限,没有模块也是能够的,可是在服务器端,必定要有模块,与操做系统和其余应用程序互动,不然根本无法编程。
node编程中最重要的思想之一就是模块,而正是这个思想,让JavaScript的大规模工程成为可能。模块化编程在js界流行,也是基于此,随后在浏览器端,requirejs和seajs之类的工具包也出现了,能够说在对应规范下,require统治了ES6以前的全部模块化编程,即便如今,在ES6 module被彻底实现以前,仍是这样。
在CommonJS中,暴露模块使用module.exports和exports,不少人不明白暴露对象为何会有两个,后面会介绍区别
在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就能够像下面这样加载。
1 var math = require('math');
而后,就能够调用模块提供的方法:
1 var math = require('math'); 2 math.add(2,3); // 5
正是因为CommonJS 使用的require方式的推进,才有了后面的AMD、CMD 也采用的require方式来引用模块的风格
AMD规范:
有了服务器端模块之后,很天然地,你们就想要客户端模块。并且最好二者可以兼容,一个模块不用修改,在服务器和浏览器均可以运行。
可是,因为一个重大的局限,使得CommonJS规范不适用于浏览器环境。若是在浏览器中运行,会有一个很大的问题
1 var math = require('math'); 2 math.add(2, 3);
第二行math.add(2, 3),在第一行require(‘math’)以后运行,所以必须等math.js加载完成。也就是说,若是加载时间很长,整个应用就会停在那里等。这对服务器端不是一个问题,由于全部的模块都存放在本地硬盘,能够同步加载完成,等待时间就是硬盘的读取时间。可是,对于客户端浏览器,这倒是一个大问题,由于模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死”状态。所以,浏览器端的模块,不能采用“同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。
AMD是“Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行。
模块必须采用特定的define()函数来定义。
1 define(id?, dependencies?, factory)
id:字符串,模块名称(可选) dependencies: 是咱们要载入的依赖模块(可选),使用相对路径。注意是数组格式 factory: 工厂方法,返回一个模块函数
若是一个模块不依赖其余模块,那么能够直接定义在define()函数之中。
1 // math.js 2 define(function (){ 3 var add = function (x,y){ 4 return x+y; 5 }; 6 return { 7 add: add 8 }; 9 });
若是这个模块还依赖其余模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。
1 define(['Lib'], function(Lib){ 2 function foo(){ 3 Lib.doSomething(); 4 } 5 return { 6 foo : foo 7 }; 8 });
当require()函数加载上面这个模块的时候,就会先加载Lib.js文件。AMD也采用require()语句加载模块,可是不一样于CommonJS,
它要求两个参数:require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功以后的回调函数。
若是将前面的代码改写成AMD形式,就是下面这样:
1 require(['math'], function (math) { 2 math.add(2, 3); 3 });
math.add()与math模块加载不是同步的,浏览器不会发生假死。因此很显然,AMD比较适合浏览器环境。目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js。
CMD规范:
CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。它写起来是这样的:
1 define(function(require, exports, module) { 2 var clock = require('clock'); 3 clock.start(); 4 });
CMD与AMD同样,也是采用特定的define()函数来定义,用require方式来引用模块
define(id?, dependencies?, factory)
id:字符串,模块名称(可选)
dependencies: 是咱们要载入的依赖模块(可选),使用相对路径。,注意是数组格式
factory: 工厂方法,返回一个模块函数
1 define('hello', ['jquery'], function(require, exports, module) { 2 // 模块代码 3 });
若是一个模块不依赖其余模块,那么能够直接定义在define()函数之中。
define(function(require, exports, module) { // 模块代码 });
注意:带 id 和 dependencies 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。
CMD与AMD区别
AMD和CMD最大的区别是对依赖模块的执行时机处理不一样,而不是加载的时机或者方式不一样,两者皆为异步加载模块;AMD依赖前置,js能够方便知道依赖模块是谁,当即加载;而CMD就近依赖,须要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是不少人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到能够忽略。