1. 模块化
1.1 不使用模块化的问题
全局变量污染:代码中的函数必须是全局变量,才能暴露给对方
变量重名:不同文件中的变量如果重名,后面的会覆盖前面的,造成程序运行错误。
文件依赖问题:不知道引用的文件里面会引用什么文件:互相依赖关系不清晰
不利于多人协作开发
1.2 使用模块化
— 每个文件作为一个模块,通过固定的方式向外暴露,也通过固定的方式引入外部模块
- 只需引用一个 js 文件,其它的根据依赖关系自动引用
- 无全局变量污染
1.3 闭包
在模块化概念提出以前,大部分会用闭包解决变量重名和全局变量污染问题,
每个js文件都用IIFE包裹,各个js文件分别在不同的此法作用域中,相互隔离,通过闭包暴露变量;
通过script标签进行引用,标签的顺序就是模块的依赖关系。
举个栗子:
//name.js
var name = 'lucy';
return name;
//info.js
var info = (function(name){
var word = 'hello' + name;
var age = 12;
return {
word: word,
age: age
}
})()
//word.js
var word = (function(info){
var word = info.word + 'hello word';
return word;
})()
//index.js
;(function(info, message){
console.log('message:', message);
console.log('age:', info.age);
})()
<script src='./name.js'></script>
<script src='./info.js'></script>
<script src='./word.js'></script>
特点:
各个js文件有自己的作用域,避免了变量重名干扰,将变量统一暴露出来,避免全局污染;
模块外部不能轻易改变闭包内部的变量,有利于程序稳定性;
模块与外部的连接通过IIFE传参,更清晰的看出依赖关系;
应对应依赖关系来决定script标签的引用顺序。
但这并不算是真正意义上的模块化。
2. 主流模块化规范发展
2.1 CommonJS
CommonJS是服务器端模块化的规范,由NodeJS推广使用。
每个文件都是一个Module实例,通过module关键字暴露内容
文件内通过require对象来引入指定模块
所有文件加载均是同步完成
模块加载一次之后就会被缓存
模块编译本质上是沙箱编译
只能在服务器端环境上运行
使用CommonJS,上面的例子可这样改写
//name.js
module.exports = {
name: 'lucy'
};
//info.js
var name = require('./name.js');
module.exports = {
word: 'hello' + name,
age: 12
};
//word.js
var info = require('./info.js');
exports.word = info.word + 'hello word';
CommonJS 只需引入一个入口文件(此处为index.js文件),无需再单独引入各依赖文件
//index.js
var info = require('./info.js');
var word = require('./word.js');
console.log('word:', word);
console.log('age:', info.age);
CommonJS基于Node原生api在服务端实现模块同步加载,但仅局限于服务端,客户端同步加载依赖时间消耗非常大,那么AMD规范诞生了。
2.2 AMD规范(Asynchronous Module Definition)
异步加载模块,模块的加载不影响它后面语句的运行,所有依赖这个模块的语句,都定义在一个回调函数中,依赖加载完后,这个回调函数才会执行。
AMD规范实际上是 RequireJS 在推广过程中产生的。
AMD规范语法:
//模块定义
define(id?(可选), dependencies?(可选), factory);
//模块引用
require([module], callback);
上面例子可简单修改为:
//name.js
define(function(){
return{
name: 'lucy'
}
});
//info.js
define(
['name'],
function(name) {
return {
word: 'hello' + name,
age: 12
}
};
});
//word.js
define(
"word",
['info'],
function(info) {
return info.word + 'hello word'
};
});
require(['info', 'word'], function (){
console.log('word:', word);
console.log('age:', info.age);
});
对于没有按照AMD规范定义的模块,可在 require() 加载之前,先使用 require.config() 方法定义他们的特征。
2.3 CMD规范
受CommonJS的启发,阿里诞生了一个CMD规范。
AMD 是依赖关系前置,而CMD是按需加载.
语法:
//模块定义
define(function(require, exports, module) {
// 模块代码
var a = require('./a')
});
//模块引用
require()
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出,CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
AMD:提前执行(异步加载:依赖先执行)+延迟执行,CMD:延迟执行(运行到需加载,根据顺序执行)
CMD 推崇依赖就近,AMD 推崇依赖前置
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// ....
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// ....
b.doSomething()
...
})
但后来seaJS逐渐不再维护,CMD规范渐渐淡出视野。
2.4 ES6 模块化
2015年,ES6推出了官方的模块化解决方案。
在 ES6 中,使用export关键字来导出模块,使用import关键字引用模块,export default,为模块指定默认输出,对应导入模块import时,不需要使用大括号
使用import导入模块时,需要知道要加载的变量名或函数名
import 先于模块内的其他模块执行,无法在运行时加载模块,import 和 export 不能在代码块之中
CommonJS 和 ES6 的区别
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用,原始值改变,import加载的值也会改变。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
上面例子用ES6模块化实现如下:
//name.js
export default {
name: 'lucy'
}
//info.js
import {name} from './name';
export default {
word: 'hello' + name,
age: 12
}
//word.js
import {name} from './name';
export const word = 'hello' + name;
import info from './info';
import {word} from './word';
console.log('word:', word);
console.log('age:', info.age);
参考:
https://segmentfault.com/a/1190000000733959?utm_source=sf-related
https://segmentfault.com/a/1190000015302578
https://segmentfault.com/a/1190000010913832#articleHeader4