起因
起初没有模块化概念之前,编写多个js文件时,必须按照顺序引入所需要的js文件
<script src="jqery.js"></script>
<script src="./a.js"></script>
<script src="./b.js"></script>
这样的缺点是
- 全局污染,每个js文件都会向全局作用域外暴露自己的变量和函数。比如:
jqery.js
会在全局环境暴露$
、jqery
函数,a.js
也在全局中暴露同样名称的函数,覆盖了jqery.js
全局函数,那么b.js
就无法在访问到jqery.js
暴露的函数 - 加载顺序,因为每个模块的执行存在关联性,并且执行和加载js模块都会阻塞js执行线程,所以js的加载顺序必须固定,比如:
a.js
需要访问jqery.js
的方法,那么jqery.js
一定要在a.js
前面加载运行
闭包
在js中,不止全局作用域,还有函数作用域,通过闭包的方式减少向外暴露的变量和方法数量
// a.js
(function (win) {
win.aModule = {
data: [1, 2, 3],
};
})(window);
// b.js
(function (win, aModule) {
win.bModule = {
data: aModule.data.concat(5, 6),
};
})(window, window.aModule);
全局污染得到缓解,但是加载顺序的问题还是无法解决
commonJS
node
提供的一套模块化方案,这里需要注意require
加载是同步的,也就是在运行到require
代码时,才会去加载其他包,并且执行也是同步的,但是这里需要注意每个模块式会被加载一次,以后加载运行都是直接使用缓存
// a.js
module.exports = {
data: [1, 2, 3],
};
// b.js
const { data } = require("./a.js");
module.exports = {
data: data.concat([5, 6]),
};
// index.js
const aModule = require("./a.js");
const bModule = require("./b.js");
console.log(aModule.data); // [1, 2, 3]
console.log(bModule.data); // [1, 2, 3, 5, 6]
解决了全局污染和加载顺序的问题,那commonJS
可以使用在浏览器端吗?
显然时不能的,在使用require
记载模块时会阻塞整个程序,在node
端完全不是问题,这是因为运行在后端,但是在浏览器端肯定时不行的,如果浏览器加载很多模块,那白屏或者浏览器卡死的时间就会非常的长,而且网速还是不可预知的
AMD
AMD
规范其实是仿照commonJS
来的,上面说过之所以在浏览器不能直接使用commonJS
规范,是因为commonJS
是同步加载同步运行的,但是在浏览器端实现同步加载时不现实的
那么AMD
案件出现了,他提出了异步加载并,已经是先加载好每个模块并且执行回去模块值之后,再去运行业务代码
// a.js
define(function () {
return {
data: [1, 2, 3],
};
});
// b.js
define(["a"], function (aModule) {
return {
data: aModule.data.concat([5, 6]),
};
});
// index.js
require.config({
paths: {
a: "./a",
b: "./b",
},
});
require(["a", "b"], function (aModule, bModule) {
console.log(aModule.data); // [1, 2, 3]
console.log(bModule.data); // [1, 2, 3, 5, 6]
});
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script src="index.js"></script>
</body>
</html>
实现一个AMD
其实也很简单,就是先根据配置加载模块并且运行(require.config.paths
就是预先加载模块的配置),在去执行业务代码的回调函数
CMD
上面在讲AMD
提到一个现象,就是AMD
在加载模块时,还会先运行先运行模块里面的代码,得到模块值,也就是提前执行、依赖前置
CMD
觉得可以提前加载模块,但是不应该提前运行模块代码,而应该在使用到这个模块的时候才运行模块代码,也就是延迟执行、依赖就近
但是这里需要注意,无论是AMD
还是CMD
,都需要提前加载模块,区别在于什么时候执行模块
// a.js
define(function () {
return {
data: [1, 2, 3],
};
});
// b.js
define(function (require) {
const aModule = require("./a.js");
return {
data: aModule.data.concat([5, 6]),
};
});
// index.js
define(function (require) {
const aModule = require("./a.js");
const bModule = require("./b.js");
console.log(aModule.data); // [1, 2, 3]
console.log(bModule.data); // [1, 2, 3, 5, 6]
});
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/seajs/3.0.3/sea.js"></script>
<script type="text/javascript">
seajs.use('./index.js');
</script>
</body>
</html>
esModel
上面介绍的都是民间开发自己研究出来的,并不具有系统性、规范性
所以在Es6
时,Es
规范提出了esModel
规范
esModel
和commonJS
一样使用同步加载依赖包,和commonJS
不同的是返回值一个引用地址
// a.js
const data = [1, 2, 3];
export default {
data,
};
//b.js
import aModule from "./a.js";
const data = aModule.data.concat([4, 5]);
export default {
data,
};
// index.js
import aModule from "./a.js";
import bModule from "./b.js";
console.log(aModule.data); // [1, 2, 3]
console.log(bModule.data); // [1, 2, 3, 5, 6]
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module" src="index.js"></script>
</body>
</html>