一、前言
這是我們經(jīng)常寫在 HTML 中的語句,引用了很多 js 文件,但是看不清他們彼此之間的依賴關(guān)系:
<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 文件還能不能正常工作。很難看出之間的依賴關(guān)系。
js 文件之間有變量的污染,為了避免麻煩進行無腦合并 js 文件時,會出現(xiàn)一個問題。它們之間有變量名沖突,所以的為每個 JS 文件都加上 IIFE(immediately invoking function express)的外殼:
(function(){
})();
(function(){
})();
(function(){
})();
(function(){
})();
(function(){
})();
這種無腦合并的隱患很大,比如一個sum函數(shù)其他的js中要用,此時是沒有定義域的。(中介者模式可以解決IIFE之間的調(diào)用window.a)
(function(){
//這個函數(shù)寫在IIFE中,
function sum(){
}
})();
(function(){
//這里面沒有sum的定義
alert(sum(3,4));
})();
(function(){
//也定義了一個sum,很亂
function sum(){
}
})();
所以這種合并,需要的不是IIFE,而是一個命名空間。
二、AMD規(guī)范 - require.js
Asynchronous Module Defination 異步模塊定義。代表庫就是 require.js。
require.js的用法 - 阮一峰的網(wǎng)絡(luò)日志 :http://www.ruanyifeng.com/blog/2012/11/require_js.html
2.1 簡單demo
使用 require.js 的第一步,是先去官方網(wǎng)站 [下載] (http://requirejs.org/docs/download.html)最新版本。
<script src="js/require.js" data-main="js/main"></script>
在main.js中寫一條語句:main可直接執(zhí)行
alert("你好");
data-main屬性的作用是,靜態(tài)化main.js,由于require.js默認的文件后綴名是js,所以可以把main.js簡寫成main。
2.2 函數(shù)暴露和模塊的引用
文件夾結(jié)構(gòu):
┣ js
┃ ┣ equire.js
┃ ┣ main.js
┃ ┣ circle.js
┣ index.html
- 函數(shù)暴露
我們使用define()來包裹一個函數(shù),define不是ECMAScript的語法,而是require.js的語法。
函數(shù)里面可以寫任何語句,需要暴露的東西,寫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
})
我們發(fā)現(xiàn),依賴注入的語法是:
require(["模塊1","模塊2","模塊3"],function(模塊1,模塊2,模塊3){
});
注意:
1)依賴的文件名必須有引號,注入的時候沒有引號的;
2)注入的時候的名字可以任取,所以require.js是通過依賴注入的順序來產(chǎn)生對應(yīng)關(guān)系的
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 設(shè)置暴露口
AMD規(guī)范最強大的事情,就是在于一個普通的JS文件如果不符合AMD規(guī)范,可以設(shè)置暴露口。
什么叫做符合AMD規(guī)范:如果一個庫在創(chuàng)建的時候有這么一條語句(偽代碼):
define(function(){
return {}
})
此時就說明對require.js兼容了,此時叫做符合AMD規(guī)范。
比如我們引入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 中定義這層關(guān)系:
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規(guī)范?(本質(zhì))
首先我們要知道依賴注入的時候,依賴的模塊們(比如下面的4個依賴)彼此之間沒有加載順序之分的。只有一個亙古不變的真理:當(dāng)他們都加載完畢之后,執(zhí)行回調(diào)函數(shù)。
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("我最先執(zhí)行");
2.7任何普通模塊也可以有依賴注入。
define(["fang","circle"],function(fang,circle){
return {
fang,
circle
}
});
這是才通過主文件調(diào)用的話,多此一舉,特別類似中介者模式;比如console.log(c.f.fang(9,9));
總結(jié),到底為什么叫做AMD規(guī)范:
1)AMD規(guī)范使用依賴注入的形式,所有的依賴項是同時加載的,沒有回來的先后之分,都回來了才執(zhí)行回調(diào)函數(shù)。也就是說,回調(diào)函數(shù)是AMD規(guī)范的特征。
2)require()里面的語句是異步語句,把依賴項都加載完畢之后才執(zhí)行回調(diào)函數(shù),此時就是“異步”的由來。
3)AMD規(guī)范中,如果兩個模塊之間有“插件”的關(guān)系,彼此依賴,可以用shim中定義這個關(guān)系。
AMD的現(xiàn)狀:很慘。
? 只有require.js對AMD進行了實現(xiàn)。
? Angular1使用了AMD規(guī)范,而Angular2放棄了AMD規(guī)范,轉(zhuǎn)入了CMD陣營,Angular4、5都是CMD的。
三、CMD規(guī)范
Common Module Definition,通用模塊定義。
它的實現(xiàn):common.js、sea.js(淘寶玉伯)、node.js。
我們看sea.js的實現(xiàn)。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總結(jié):
1)nodejs是遵循CMD規(guī)范的,可以裸奔CMD規(guī)范。
2)所有的模塊都要用define(function(require,exports,module){})包裹,稱為“標(biāo)準(zhǔn)殼”;
3)暴露有兩種途徑exports.** = ** ; module.exports = ** 。
4)引用的時候用require()引用,require誰就執(zhí)行誰,會死等這個文件加載完畢,沒有回調(diào)函數(shù)。
5)CMD規(guī)范中沒有node_modules這個神奇的文件夾的概念,是nodejs自己添加的特性。
最后說一嘴:AMD、CMD規(guī)范和業(yè)務(wù)一點關(guān)系沒有,就是純粹的文件組織的形式。網(wǎng)頁DOM開發(fā)是不會用AMD、CMD規(guī)范的,現(xiàn)在AMD、CMD學(xué)習(xí)的意義就是服務(wù)于Angular、React、Vue的。