一、前言
这是我们经常写在 HTML 中的语句,引用了很多 js 文件,但是看不清他们彼此之间的依赖关系:
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>
删除 1.js、2.js 之后,其他的 js 文件还能不能正常工作。很难看出之间的依赖关系。
js 文件之间有变量的污染,为了避免麻烦进行无脑合并 js 文件时,会出现一个问题。它们之间有变量名冲突,所以的为每个 JS 文件都加上 IIFE(immediately invoking function express)的外壳:
(function(){
})();
(function(){
})();
(function(){
})();
(function(){
})();
(function(){
})();
这种无脑合并的隐患很大,比如一个sum函数其他的js中要用,此时是没有定义域的。(中介者模式可以解决IIFE之间的调用window.a)
(function(){
//这个函数写在IIFE中,
function sum(){
}
})();
(function(){
//这里面没有sum的定义
alert(sum(3,4));
})();
(function(){
//也定义了一个sum,很乱
function sum(){
}
})();
所以这种合并,需要的不是IIFE,而是一个命名空间。
二、AMD规范 - require.js
Asynchronous Module Defination 异步模块定义。代表库就是 require.js。
require.js的用法 - 阮一峰的网络日志 :http://www.ruanyifeng.com/blog/2012/11/require_js.html
2.1 简单demo
使用 require.js 的第一步,是先去官方网站 [下载] (http://requirejs.org/docs/download.html)最新版本。
<script src="js/require.js" data-main="js/main"></script>
在main.js中写一条语句:main可直接执行
alert("你好");
data-main属性的作用是,静态化main.js,由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
2.2 函数暴露和模块的引用
文件夹结构:
┣ js
┃ ┣ equire.js
┃ ┣ main.js
┃ ┣ circle.js
┣ index.html
- 函数暴露
我们使用define()来包裹一个函数,define不是ECMAScript的语法,而是require.js的语法。
函数里面可以写任何语句,需要暴露的东西,写return来暴露。
define(function(){
function mianji(a,b){
return a * b;
}
return {
mianji //k、v相同,省略v
}
});
- 模块的引用
主文件需要使用“依赖注入”(dependencies injection)的方式引入这个模块。
require(['circle'],function(c){
alert(c.mianji(10,10)); //100
})
我们发现,依赖注入的语法是:
require(["模块1","模块2","模块3"],function(模块1,模块2,模块3){
});
注意:
1)依赖的文件名必须有引号,注入的时候没有引号的;
2)注入的时候的名字可以任取,所以require.js是通过依赖注入的顺序来产生对应关系的
3)依赖的时候,没有拓展名,模块的默认名字就是文件名。
2.3 别名
模块的名字默认是文件名。
require(['yuan','fang'],function(y,f){
alert(y.mianji(10));
alert(f.mianji(10,20));
});
如果我们想把依赖的名字改掉,此时可以使用require对象的config方法来配置paths项:
require.config({
paths: {
"circle" : "yuan" //没有.js扩展名,后面的是原文件名,前面的是别名
}
});
require(['circle','fang'],function(y,f){
alert(y.mianji(10));
alert(f.mianji(10,20));
});
2.4 设置暴露口
AMD规范最强大的事情,就是在于一个普通的JS文件如果不符合AMD规范,可以设置暴露口。
什么叫做符合AMD规范:如果一个库在创建的时候有这么一条语句(伪代码):
define(function(){
return {}
})
此时就说明对require.js兼容了,此时叫做符合AMD规范。
比如我们引入jquery库:
require.config({
paths: {
"circle" : "yuan",
"jq" : "lib/jquery.min"
}
});
require(['circle','fang','jq'],function(y,f,$){
alert(y.mianji(10));
alert(f.mianji(10,20));
$("#box").css("background-color" , "red");
});
----
main.js:9 Uncaught TypeError: $ is not a function
at main.js:9
at Object.execCb (require.js:5)
at e.check (require.js:5)
at e.<anonymous> (require.js:5)
at require.js:5
at require.js:5
at each (require.js:5)
at emit (require.js:5)
at e.check (require.js:5)
at enable (require.js:5)
2.5 引入有依赖的依赖的库
比如我们引入 jquery-ui,很明显 jquery-ui 是 jquery 的插件。此时我们说 jquery-ui依 赖jquery。
此时我们要在 shim 中定义这层关系:
require.config({
paths: {
"circle" : "yuan",
"jq" : "lib/jquery.min",
"jqui" : "lib/jquery-ui.min"
},
shim: {
'jq': {
exports : '$'
},
'jqui': {
deps: ['jq'] //这是重点
}
}
});
require(['circle','fang','jq','jqui'],function(y,f,$,jqui){
alert(y.mianji(10));
alert(f.mianji(10,20));
$("#box").animate({"font-size":400},1000);
$("#box").draggable();
});
测试如果引入依赖文件可以不写这步的操作。
2.6为什么叫AMD规范?(本质)
首先我们要知道依赖注入的时候,依赖的模块们(比如下面的4个依赖)彼此之间没有加载顺序之分的。只有一个亘古不变的真理:当他们都加载完毕之后,执行回调函数。
require(['circle','fang','jq','jqui'],function(y,f,$,jqui){
alert(y.mianji(10));
alert(f.mianji(10,20));
$("#box").animate({"font-size":400},1000);
$("#box").draggable();
});
alert("我最先执行");
2.7任何普通模块也可以有依赖注入。
define(["fang","circle"],function(fang,circle){
return {
fang,
circle
}
});
这是才通过主文件调用的话,多此一举,特别类似中介者模式;比如console.log(c.f.fang(9,9));
总结,到底为什么叫做AMD规范:
1)AMD规范使用依赖注入的形式,所有的依赖项是同时加载的,没有回来的先后之分,都回来了才执行回调函数。也就是说,回调函数是AMD规范的特征。
2)require()里面的语句是异步语句,把依赖项都加载完毕之后才执行回调函数,此时就是“异步”的由来。
3)AMD规范中,如果两个模块之间有“插件”的关系,彼此依赖,可以用shim中定义这个关系。
AMD的现状:很惨。
只有require.js对AMD进行了实现。
Angular1使用了AMD规范,而Angular2放弃了AMD规范,转入了CMD阵营,Angular4、5都是CMD的。
三、CMD规范
Common Module Definition,通用模块定义。
它的实现:common.js、sea.js(淘宝玉伯)、node.js。
我们看sea.js的实现。https://www.zhangxinxu.com/sp/seajs/
//index.html
<body>
<script src="js/sea.js"></script>
<script>
seajs.use("./js/main.js");
</script>
</body>
//main.js:
define(function(require,exports,module){
var yuan = require("./yuan.js");
var fang = require("./fang.js");
alert(yuan.mianji(10))
alert(fang.mianji(10,20))
});
//yuan.js:
define(function(require,exports,module){
function mianji(r){
return r * r * 3.14;
}
exports.mianji = mianji;
});
CMD总结:
1)nodejs是遵循CMD规范的,可以裸奔CMD规范。
2)所有的模块都要用define(function(require,exports,module){})包裹,称为“标准壳”;
3)暴露有两种途径exports.** = ** ; module.exports = ** 。
4)引用的时候用require()引用,require谁就执行谁,会死等这个文件加载完毕,没有回调函数。
5)CMD规范中没有node_modules这个神奇的文件夹的概念,是nodejs自己添加的特性。
最后说一嘴:AMD、CMD规范和业务一点关系没有,就是纯粹的文件组织的形式。网页DOM开发是不会用AMD、CMD规范的,现在AMD、CMD学习的意义就是服务于Angular、React、Vue的。