CommonJS, AMD, CMD , 其實(shí)很早就接觸過了。
當(dāng)時(shí), 網(wǎng)上的文章看得眼花繚亂, 只依稀記得幾個(gè)模糊的概念。
什么依賴前置 , 什么按需加載。
一頭霧水。
現(xiàn)在再回過頭來看看概念 , 網(wǎng)上部分文章用詞模棱兩可。
給我們這些菜雞, 帶來了理解的偏差和困惑。
記得第一個(gè)項(xiàng)目還用了requireJS 。
時(shí)過境遷,現(xiàn)在入門前端 , 都是直接上webpack了 。
但我覺得還是有必要理一理 。
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)中提出來的 (但是在Google和Stack Overflow, 這個(gè)概念很少被提到, 一般出現(xiàn)在國(guó)內(nèi))
。
CommonJS規(guī)范, 模塊加載是同步的對(duì)node來說,模塊存放在本地硬盤,同步加載,等待時(shí)間就是硬盤的讀取時(shí)間,這個(gè)時(shí)間非常短;
AMD、CMD 規(guī)范,模塊加載是異步的目的, 是為了適應(yīng)瀏覽器環(huán)境,加載的時(shí)間取決于網(wǎng)絡(luò)的好壞,可能要等很長(zhǎng)時(shí)間;
記得看文章的時(shí)候, 看到了下面這段話 ,
腳本標(biāo)簽天生是異步的
, 那為什么會(huì)出現(xiàn) async 和 defer ?加載
和執(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
以后, 下載的過程就不會(huì)阻塞dom渲染了, 但腳本的執(zhí)行是在dom渲染完畢之后;
<script async>
在加上async
以后, 下載的過程同樣不會(huì)阻塞dom渲染, 但腳本會(huì)在下載完后立刻執(zhí)行, 所以存在多個(gè)<script async>
時(shí), 無法保證多個(gè)js文件的執(zhí)行順序, 加載較快的腳本會(huì)執(zhí)行;
所以defer, async主要作用于加載階段, 執(zhí)行階段仍然會(huì)阻塞dom渲染
再看看使用require.js的模塊寫法
新建 main.js / a.js / b.js , main.js 為入口, 引用了a.js , b.js
// main.js // waitSeconds = 0的配置, 是為了防止文件過大或網(wǎng)絡(luò)不佳時(shí), 加載時(shí)間過長(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 的加載或者說下載是并行的, 但 a 和 b 的執(zhí)行順序不確定的 , a 和 b 先執(zhí)行哪一個(gè)都無所謂 ,只需要保證回調(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.w3.org/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
,
另外, 前面說 , 依賴的模塊都執(zhí)行完之后, 才會(huì)執(zhí)行回調(diào)函數(shù)。 那怎么判斷是否 所有依賴的模塊 都已經(jīng)執(zhí)行完 ?
多個(gè)模塊的情況 , 還沒看懂(捂臉) , 但是單個(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; ... ...
上面的代碼可以看到, 通過 <script>
標(biāo)簽的onload事件可以判斷, 該腳本是否執(zhí)行完畢 ;
所以, 個(gè)人理解, require.js的異步
第一, 是指下載的異步,
第二, 還指回調(diào)機(jī)制, 依賴模塊執(zhí)行完之后再執(zhí)行回調(diào)函數(shù)
現(xiàn)在 再來看 AMD 和 CMD 的區(qū)別, 網(wǎng)上的說法:
AMD推崇依賴前置,在定義模塊的時(shí)候就要聲明其依賴的模塊
CMD推崇就近依賴,只有在用到某個(gè)模塊的時(shí)候再去require
第二點(diǎn) 只有在用到某個(gè)模塊的時(shí)候再去require
, 這種說其實(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
語句的時(shí)候 , 再執(zhí)行依賴模塊;
AMD和CMD最大的區(qū)別是: 對(duì)依賴模塊的執(zhí)行時(shí)機(jī)處理不同(注意不是加載的時(shí)機(jī))
很多人說, requireJS是異步加載模塊,SeaJS是同步加載模塊,這么說實(shí)際上是不準(zhǔn)確的 ;
二者加載模塊都是異步的 ;
只不過AMD依賴前置,可以方便知道依賴了哪些模塊,然后馬上加載 , 在加載完成后, 就會(huì)執(zhí)行該模塊;
而CMD推崇就近依賴,把模塊變?yōu)樽址馕鲆槐? 找到依賴了哪些模塊, 在加載模塊完成后, 不立刻執(zhí)行, 而是等到require
后再執(zhí)行;
上面只說了異步相關(guān)的概念, 其實(shí) require.js / sea.js , 最重要的還是模塊化。
模塊化降低耦合,依賴清晰,讓調(diào)試, 加功能, 任務(wù)分配和交接都更方便。
聯(lián)系客服