阶段1-文件划分
将每个功能单独放在一个文件中,约定每一个文件是一个模块,当引用这个模块时,通过script标签引入到html页面中,直接使用模块中的方法或成员变量。
// moduleA.js
var name = 'moduleA';
function say(){
console.log('say i am'+name)
}
function write(){
console.log('write i am'+name)
}
// moduleB.js
var name = 'moduleB';
function say(){
console.log('say i am'+name)
}
function write(){
console.log('write i am'+name)
}
// test.html
<script src="moduleA.js"></script>
<script src="moduleB.js"></script>
<script>
// 冲突
say();
// 变量可被修改
name = 'test';
</script>
缺点:
所有的变量全部暴露在全局中,都可以被随时访问和修改,随着文件的增多,很难通过约定避免命名冲突
阶段2-命名空间
// moduleC
var moduleC = {
name: 'moduleC',
say: function(){
console.log('say i am'+name)
}
}
var moduleD = {
name: 'moduleD',
say: function(){
console.log('say i am'+name)
}
}
// test.html
<script src="moduleA.js"></script>
<script src="moduleB.js"></script>
<script>
moduleC.say(); // 正常调用
moduleC.name = 'test'; // 模块中的成员变量可被随意修改
</script>
缺点:命名空间的方法极大程度的减小了命名冲突的可能,但是也存在成员变量可能被随意修改的风险
阶段3-立即执行函数(IIFE)
// moduleE
var moduleE = (function(){
var name = 'moduleE';
function say(){
console.log('say i am '+name)
}
return {
say: say
}
})()
// moduleF
var moduleF = (function(){
var name = 'moduleF';
function say(){
console.log('say i am '+name)
}
return {
say: say
}
})()
<script src="moduleE.js"></script>
<script src="moduleF.js"></script>
<script>
moduleE.say(); // 正常调用
moduleF.say(); // 正常调用
console.log(moduleE.name) //undefined
</script>
实现了成员私有化,模块内的变量外界不能随意访问修改,通过立即执行函数也可以传递模块所需要的依赖,使得模块对外部的依赖更加清晰
模块化规范
CommonJS
CommonJS规定每个文件都是一个模块,有独立的作用域。每个模块内部都有一个module对象代表当前模块,通过它来导出当前模块
Commonjs的规范有以下特点:
- 文件即模块,文件内所有代码都运行在独立的作用域,不会污染全局空间
- 模块可以被多次引用。在第一次被加载时就会被缓存,之后都从缓存中读取
- 加载某个模块就是引入该模块的module.exports属性
- module.exports属性输出的是值的拷贝,一旦值被输出,模块内的变化不会影响到输出的值
- 模块的加载顺序按照代码的引入顺序
// m1.js
module.exports = {
name: 'm1',
say: function(){
console.log(this.name)
}
}
//m2.js
exports.test = function(){
console.log('m2')
}
var m1 = require('./m1');
var m2 = require('./m2');
m1.say(); // m1
m2.test(); // m2
exports可以理解为module.exports的引用,可以给exports对象添加方法,但不能直接赋值。因为这样会切断exports与module.exports的联系。
AMD
由于Commonjs规范加载模块是同步的,不适合浏览器环境,所以出现了异步加载的模块化标准AMD,
特点:同时并发加载所有依赖模块之后再执行当前模块的回调函数
代表库:require.js
require( [ ] ,function( ){ } )是require.js的核心之一,它接受两个参数。第一个参数是一个数组,表示所依赖的模块,第二个参数是一个回调函数,当前面指定的模块加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
...
});
// 定义一个模块
define(function(){
var name = 'moduleJ';
var sayHi = function(){
console.log('i am require.js')
}
return {
name: name,
sayHi: sayHi
};
})
// 引用模块
require(['moduleJ'], function(){
moduleJ.sayHi();
})
CMD
CMD 规范整合了 CommonJS 和 AMD 规范的特点
代表库: sea.js
CMD 最大的特点就是懒加载,不需要在定义模块的时候声明依赖,可以在模块执行时动态加载依赖。当然还有一点不同,那就是 CMD 同时支持同步加载模块和异步加载模块。
define(function(require, exports, module) {
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
module.id = "increment";
});
AMD 和 CMD 的两个主要区别
- AMD需要异步加载模块,而CMD可以用同步的方式require依赖,也可以用require.async这种异步的方式
- CMD遵循依赖就近原则,AMD遵循依赖前置原则。也就是说,在 AMD 中,我们需要把模块所需要的依赖都提前在依赖数组中声明。而在 CMD 中,我们只需要在具体代码逻辑内,使用依赖前,把依赖的模块 require 进来
ES6模块化
CommonJS和AMD都是在运行时确定依赖关系,即运行时加载,CommonJS加载的是拷贝,而ES6 module则是在编译时就确定依赖关系,所有加载的其实都是引用
// 导出
var first = 'test';
var second = 'test';
function func() {
return true;
}
export {first, second, func};
//引用
import {first} from './a'
- export default会导出默认输出,即用户不需要知道模块中输出的名字,在导入的时候为其指定任意名字。导入默认模块时不需要大括号,导出默认的变量或方法可以有名字,但是对外无效。export default只能使用一次。