hello~親愛的看官老爺們你們好~上週寫下一篇 簡單易懂的 webpack 打包後 JS 的運行過程 後,仍是挺受小夥伴們歡迎的。然而這篇文章挖了坑還沒填完,此次就把剩下的內容補完。css
本文主要是關於異步加載的 js
是如何執行,較少使用 webpack
問題也不大。而若是看過前一篇文章相關的知識那就更好了。若已經瞭解過相關知識的小夥伴,不妨快速閱讀一下,算是溫故知新,實際上是想請你告訴我哪裏寫得不對。html
webpack
的配置就不貼出來了,就是肯定一下入口,提取 webpack
運行時須要用到的 minifest.js
而已。這裏簡單貼一下 html
模板與須要的兩個 js
文件:webpack
<!--index.html-->
<!doctype html>
<html lang="en">
<body>
<p class="p">Nothing yet.</p>
<button class="btn">click</button>
</body>
</html>
//index.js
const p = document.querySelector('.p');
const btn = document.querySelector('.btn');
btn.addEventListener('click', function() {
//只有觸發事件纔回家再對應的js 也就是異步加載
require.ensure([], function() {
const data = require('./src/js/test');
p.innerHTML = data;
})
})
//test.js
const data = 'success!';
module.exports = data;
複製代碼
這樣配置示例配置就完成了。可能有小夥伴不太熟悉 require.ensure
,簡單地說,就是告訴 webpack
,請懶加載 test.js
,別一打開頁面就給我下載下來。相關的知識不妨看這裏。git
打包完的目錄架構畫風是這樣的:github
至此,配置就完成啦~web
index.js
開始探索先用瀏覽器打開 index.html
,查看資源加載狀況,能發現只加載了 index.js
與 minifest.js
:數組
以後點擊按鈕,會再加多一個 0.7f0a.js
:promise
能夠說明代碼是被分割了的,只要當對應的條件觸發時,瀏覽器纔會去加載指定的資源。而不管以後咱們點擊多少次,0.7f0a.js
文件都不會重複加載,此時小本本應記下第一個問題:如何作到不重複加載。瀏覽器
按照加載順序,實際上是應該先看 minifest.js
的,但不妨先看看 index.js
的代碼,帶着問題有助於尋找答案。代碼以下:網絡
webpackJsonp([1], {
"JkW7":
(function(module, exports, __webpack_require__) {
const p = document.querySelector('.p');
const btn = document.querySelector('.btn');
btn.addEventListener('click', function() {
__webpack_require__.e(0).then((function() {
const data = __webpack_require__("zFrx");
p.innerHTML = data;
}).bind(null, __webpack_require__)).catch(__webpack_require__.oe)
})
})
}, ["JkW7"]);
複製代碼
可能有些小夥伴已經忘記了上一篇文章的內容,__webpack_require__
做用是加載對應 module
的內容。這裏提一句, module
其實就是打包前,import
或者 require
的一個個 js
文件,如test.js
與 index.js
。後文說到的 chunk
是打包後的文件,即 index.ad23.js
、manifest.473d.js
與 0.7f0a.js
文件。一個 chunk
可能包含若干 module
。
回憶起相關知識後,咱們看看異步加載到底有什麼不一樣。index.js
中最引入注目的應該是 __webpack_require__.e
這個方法了,傳入一個數值以後返回一個 promise
。這方法當 promise
決議成功後執行切換文本的邏輯,失敗則執行 __webpack_require__.oe
。於是小本本整理一下,算上剛纔的問題,須要爲這些問題找到答案:
__webpack_require__.e
方法的邏輯。__webpack_require__.oe
方法的邏輯。minifest.js
中尋找答案咱們先查看一下 __webpack_require__.e
方法,爲方法查看起見,貼一下對應的代碼,你們不妨先試着本身尋找一下剛纔問題的答案。
var installedChunks = {
2: 0
};
__webpack_require__.e = function requireEnsure(chunkId) {
var installedChunkData = installedChunks[chunkId];
if (installedChunkData === 0) {
return new Promise(function(resolve) {
resolve();
});
}
if (installedChunkData) {
return installedChunkData[2];
}
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = "js/" + chunkId + "." + {
"0": "7f0a",
"1": "ad23"
}[chunkId] + ".js";
script.onerror = script.onload = onScriptComplete;
function onScriptComplete() {
script.onerror = script.onload = null;
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
if (chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
installedChunks[chunkId] = undefined;
}
};
head.appendChild(script);
return promise;
};
複製代碼
該方法中接受一個名爲 chunkId
的參數,返回一個 promise
,印證了咱們閱讀 index.js
時的猜測,也確認了傳入的數字是 chunkId
。以後變量 installedChunkData
被賦值爲對象 installedChunks
中鍵爲 chunkId
的值,能夠推想出 installedChunks
對象其實就是記錄已加載 chunk
的地方。此時咱們還沒有加載對應模塊,理所固然是 undefined
。
以後咱們想跳過兩個判斷,查看一下 __webpack_require__.e
方法返回值的 promise
是怎樣的:
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
複製代碼
能夠看到 installedChunkData
與 installedChunks[chunkId]
被從新賦值爲一個數組,存放着返回值 promise
的 resolve
與 reject
,而使人不解的是,爲什麼將數組的第三項賦值爲這個 promise
呢?
其實此前有一個條件判斷:
if (installedChunkData) {
return installedChunkData[2];
}
複製代碼
那你明白爲何了嗎?在此例中1,假設網絡不好的狀況下,咱們瘋狂點擊按鈕,爲避免瀏覽器發出若干個請求,經過條件判斷都返回同一個 promise
,當它決議後,全部掛載在它之上的 then
方法都能獲得結果運行下去,至關於構造了一個隊列,返回結果後按順序執行對應方法,此處仍是十分巧妙的。
以後就是創造一個 script
標籤插入頭部,加載指定的 js
了。值得關注的是 onScriptComplete
方法中的判斷:
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
...
}
複製代碼
明明 installedChunks[chunkId]
被賦值爲數組,它確定不可能爲0啊,這不是鐵定失敗了麼?先別急,要知道 js
文件下載成功以後,先執行內容,再執行 onload
方法的,那麼它的內容是什麼呢?
webpackJsonp([0], {
"zFrx":
(function(module, exports) {
const data = 'success!';
module.exports = data;
})
});
複製代碼
能夠看到,和 index.js
仍是很像的。這個 js
文件的 chunkId
是0。它的內容很簡單,只不過是 module.exports
出去了一些東西。關鍵仍是 webpackJsonp
方法,此處截取關鍵部分:
var resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
while (resolves.length) {
resolves.shift()();
}
複製代碼
當它執行的時候,會判斷 installedChunks[chunkId]
是否存在,若存在則往數組中 push(installedChunks[chunkId][0])
並將 installedChunks[chunkId]
賦值爲0; 。還得記得數組的首項是什麼嗎?是 __webpack_require__.e
返回 promise
的 resolve
!以後執行這個 resolve
。固然, webpackJsonp
方法會將下載下來文件全部的 module
存起來,當 __webpack_require__
對應 modulIde
時,返回對應的值。
讓咱們目光返回 __webpack_require__.e
方法。 已知對應的 js
文件下載成功後,installedChunks[chunkId]
被賦值爲0。文件執行完或下載失敗後都會觸發 onScriptComplete
方法,在該方法中,如若 installedChunks[chunkId] !== 0
,這是下載失敗的狀況,那麼此時 installedChunks[chunkId]
的第二項是返回 promise
的 reject
,執行這個 reject
以拋出錯誤:
if (chunk !== 0) {
if (chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
installedChunks[chunkId] = undefined;
}
複製代碼
當再次請求同一文件時,因爲對應的 module
已經被加載,於是直接返回一個成功的 promise
便可,對應的邏輯以下:
var installedChunkData = installedChunks[chunkId];
if (installedChunkData === 0) {
return new Promise(function(resolve) {
resolve();
});
}
複製代碼
最後看一下 __webpack_require__.oe
方法:
__webpack_require__.oe = function(err) { console.error(err); throw err; };
複製代碼
特別簡單對吧?最後整理一下流程:當異步請求文件發起時,先判斷該 chunk
是否已被加載,是的話直接返回一個成功的 promise
,讓 then
執行的函數 require
對應的 module
便可。否則則構造一個 script
標籤加載對應的 chunk
,下載成功後掛載該 chunk
內全部的 module
。下載失敗則打印錯誤。
以上就是 webpack
異步加載 js
文件過程的簡單描述,其實流程真的特別簡單易懂,只是代碼的編寫十分巧妙,值得仔細研究學習。對應的代碼會放到 github 中,歡迎查閱點 star
。
感謝各位看官大人看到這裏,知易行難,但願本文對你有所幫助~謝謝!