1.牢騷CommonJS, AMD, CMD , 其實(shí)很早就接觸過(guò)了。 當(dāng)時(shí), 網(wǎng)上的文章看得眼花繚亂, 只依稀記得幾個(gè)模糊的概念。 什么依賴前置 , 什么按需加載。 一頭霧水。 現(xiàn)在再回過(guò)頭來(lái)看看概念 , 網(wǎng)上部分文章用詞模棱兩可。 給我們這些菜雞, 帶來(lái)了理解的偏差和困惑。 記得第一個(gè)項(xiàng)目還用了requireJS 。 時(shí)過(guò)境遷,現(xiàn)在入門前端 , 都是直接上webpack了 。 但我覺(jué)得還是有必要理一理 。
2. 是什么CommonJS, AMD, CMD是規(guī)范, 理念 ; 對(duì) CommonJS 的實(shí)現(xiàn) , 有 node 的模塊系統(tǒng) ; 對(duì) AMD 的實(shí)現(xiàn)有require.js ; 而 CMD, 是在sea.js的實(shí)現(xiàn)中提出來(lái)的 (但是在Google和Stack Overflow, 這個(gè)概念很少被提到, 一般出現(xiàn)在國(guó)內(nèi)) 。
CommonJS規(guī)范, 模塊加載是同步的
對(duì)node來(lái)說(shuō),模塊存放在本地硬盤,同步加載,等待時(shí)間就是硬盤的讀取時(shí)間,這個(gè)時(shí)間非常短; AMD、CMD 規(guī)范,模塊加載是異步的
目的, 是為了適應(yīng)瀏覽器環(huán)境,加載的時(shí)間取決于網(wǎng)絡(luò)的好壞,可能要等很長(zhǎng)時(shí)間;
3. 先說(shuō) async 和 defer記得看文章的時(shí)候, 看到了下面這段話 ,
 我看了半天, 總覺(jué)得不是很理解 , 為什么呢 ? 這里說(shuō), 腳本標(biāo)簽天生是異步的 , 那為什么會(huì)出現(xiàn) async 和 defer ? 然后, 我發(fā)覺(jué)我并沒(méi)有把 加載 和執(zhí)行 的概念區(qū)分清楚 , 這里的加載我把它理解為瀏覽器中的下載 這里貼一張圖就很清楚了:
 <script> 標(biāo)簽, 在下載和執(zhí)行的時(shí)候 , 會(huì)阻塞dom的渲染進(jìn)程 , 所以如果把<script> 標(biāo)簽放在<head> 中, 當(dāng) js 文件很大或者網(wǎng)絡(luò)差時(shí), 會(huì)導(dǎo)致頁(yè)面長(zhǎng)時(shí)間空白( 順帶提一下, <script>標(biāo)簽并不會(huì)阻止其他的<script>標(biāo)簽的下載, 現(xiàn)代瀏覽器中多個(gè)<script>下載是并行的, 在chrome中, 默認(rèn)支持6個(gè)資源(http1.x)并行下載 ) , 另外 , 腳本是按照<script> 標(biāo)簽的書寫順序執(zhí)行的 ;
<script defer> 在加上defer 以后, 下載的過(guò)程就不會(huì)阻塞dom渲染了, 但腳本的執(zhí)行是在dom渲染完畢之后;
<script async> 在加上async 以后, 下載的過(guò)程同樣不會(huì)阻塞dom渲染, 但腳本會(huì)在下載完后立刻執(zhí)行, 所以存在多個(gè)<script async> 時(shí), 無(wú)法保證多個(gè)js文件的執(zhí)行順序, 加載較快的腳本會(huì)執(zhí)行;
所以defer, async主要作用于加載階段, 執(zhí)行階段仍然會(huì)阻塞dom渲染
4. 再看 require.js 的異步體現(xiàn)再看看使用require.js的模塊寫法 新建 main.js / a.js / b.js , main.js 為入口, 引用了a.js , b.js // main.js
// waitSeconds = 0的配置, 是為了防止文件過(guò)大或網(wǎng)絡(luò)不佳時(shí), 加載時(shí)間過(guò)長(zhǎng)導(dǎo)致require報(bào)`Load timeout for modules`的錯(cuò)誤
require.config({
waitSeconds: 0
});
require(['a.js', 'b.js'], function(a, b){
// handle / use a, b
console.log(a)
console.log(b)
})
// a.js ------------------------------
define([], function(){
return {
a: 111111111111
}
})
// b.js ------------------------------
define([], function(){
return {
b: 222222222222
}
})
文件 開始下載 的 順序: main, a, b
為什么文件下載的順序是 main, a, b 呢? main依賴了 a b, 不是 a b 先下載嗎? 那是因?yàn)?,只?main 加載之后,才知道m(xù)ian依賴了啥啊 執(zhí)行的 順序 : a, b, main 或者 b, a, main
這里體現(xiàn) require.js 的異步加載。 a 和 b 的加載或者說(shuō)下載是并行的, 但 a 和 b 的執(zhí)行順序不確定的 , a 和 b 先執(zhí)行哪一個(gè)都無(wú)所謂 ,只需要保證回調(diào)函數(shù)在 a 和 b 都執(zhí)行完之后再執(zhí)行就可以了;
在require.js中模塊加載是怎么實(shí)現(xiàn)的呢? 看一下require.js的源碼: /**
* Creates the node for the load command. Only used in browser envs.
*/
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www./1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};
這段代碼, 新建了script標(biāo)簽, 并把它的 async 設(shè)置為true , 另外, 前面說(shuō) , 依賴的模塊都執(zhí)行完之后, 才會(huì)執(zhí)行回調(diào)函數(shù)。 那怎么判斷是否 所有依賴的模塊 都已經(jīng)執(zhí)行完 ? 多個(gè)模塊的情況 , 還沒(méi)看懂(捂臉) , 但是單個(gè)模塊的執(zhí)行狀態(tài)是可以監(jiān)聽的: ...
...
...
//mentioned above about not doing the 'script execute,
//then fire the script load event listener before execute
//next script' that other browsers do.
//Best hope: IE10 fixes the issues,
//and then destroys all installs of IE 6-9.
//node.attachEvent('onerror', context.onScriptError);
} else {
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
}
node.src = url;
...
...
上面的代碼可以看到, 通過(guò) <script> 標(biāo)簽的onload事件可以判斷, 該腳本是否執(zhí)行完畢 ; 所以, 個(gè)人理解, require.js的異步 第一, 是指下載的異步, 第二, 還指回調(diào)機(jī)制, 依賴模塊執(zhí)行完之后再執(zhí)行回調(diào)函數(shù) 5. AMD 和 CMD 的理解誤區(qū)現(xiàn)在 再來(lái)看 AMD 和 CMD 的區(qū)別, 網(wǎng)上的說(shuō)法: AMD推崇依賴前置,在定義模塊的時(shí)候就要聲明其依賴的模塊 CMD推崇就近依賴,只有在用到某個(gè)模塊的時(shí)候再去require
第二點(diǎn) 只有在用到某個(gè)模塊的時(shí)候再去require , 這種說(shuō)其實(shí)是帶有誤導(dǎo)性的, 看看sea.js的寫法: define(function(require, exports, module) {
console.log(123)
var a = require('a.js')
console.log(a)
var b = require('b.js')
console.log(b)
})
這里, 難道是執(zhí)行到require , 才去加載/下載require 的文件嗎 ? 當(dāng)然不是 ! 看一下sea.js的代碼: window.define = function(callback) {
var id = getCurrentJs()
var depsInit = s.parseDependencies(callback.toString())
var a = depsInit.map(item => basepath item)
....
sea.js把callback回調(diào)函數(shù)用轉(zhuǎn)換成字符串, 再找出有哪些依賴, 這些依賴模塊同樣是預(yù)先加載的 , 不同在于, require.js會(huì)立刻執(zhí)行依賴模塊, 而sea.js 在遇到 require 語(yǔ)句的時(shí)候 , 再執(zhí)行依賴模塊; 5. 總結(jié)AMD和CMD最大的區(qū)別是: 對(duì)依賴模塊的執(zhí)行時(shí)機(jī)處理不同(注意不是加載的時(shí)機(jī)) 很多人說(shuō), requireJS是異步加載模塊,SeaJS是同步加載模塊,這么說(shuō)實(shí)際上是不準(zhǔn)確的 ; 二者加載模塊都是異步的 ; 只不過(guò)AMD依賴前置,可以方便知道依賴了哪些模塊,然后馬上加載 , 在加載完成后, 就會(huì)執(zhí)行該模塊; 而CMD推崇就近依賴,把模塊變?yōu)樽址馕鲆槐? 找到依賴了哪些模塊, 在加載模塊完成后, 不立刻執(zhí)行, 而是等到require 后再執(zhí)行; 上面只說(shuō)了異步相關(guān)的概念, 其實(shí) require.js / sea.js , 最重要的還是模塊化。 模塊化降低耦合,依賴清晰,讓調(diào)試, 加功能, 任務(wù)分配和交接都更方便。
|