給一個裝修公司怎么做網(wǎng)站今日新聞播報
模塊循環(huán)依賴問題
在項目比較小的時候可能不怎么會遇到這個問題,但項目一旦有一定的體量后就可能會遇到了。
我之前做項目時就遇到這個問題,也是總結(jié)一篇文章。
比如這種類型的報錯
commonjs存在的問題
先講一下commonjs存在的問題。
CommonJS模塊采用深度優(yōu)先遍歷,并且是加載時執(zhí)行,即腳本代碼在require時就全部執(zhí)行。一旦出現(xiàn)某個模塊被“循環(huán)加載”,就只輸出已經(jīng)執(zhí)行的部分,沒有執(zhí)行的部分不會輸出。
舉例子
// a.js
require("./b.js");
exports.a = function () {};// b.js
const { a } = require("./a");
a();// index.js
require("./a.js");
執(zhí)行index.js
結(jié)果:報錯a is not function
執(zhí)行流程
1 導(dǎo)入a.js
require("a.js")
// 此時moduleCache
moduleCache = {moduleA : {}
}
2 執(zhí)行a.js為moduleA添加屬性,發(fā)現(xiàn)第一行導(dǎo)入b.js,模塊a還沒執(zhí)行完,執(zhí)行b.js
require("./b.js");
// 此時moduleCache
moduleCache = {moduleA : {},moduleB : {}
}
3 執(zhí)行b.js,發(fā)現(xiàn)導(dǎo)入a.js,此時moduleCache有moduleA,不會重復(fù)執(zhí)行模塊a的代碼,會直接用moduleCache中模塊a已經(jīng)導(dǎo)出的內(nèi)容。
const { a } = require("./a");
等價于
const {a} = moduleCache.moduleA
因為此時模塊a的內(nèi)容還未完全執(zhí)行完,所以解構(gòu)的變量a是undefined,還不是function,所以報錯。
webpack打包結(jié)果分析
// a.js
import "./b.js";
export const A = () => {};// b.js
import { A } from "./a.js";
A();// index.js
import "./a";
webpack打包結(jié)果
(() => {"use strict";var __webpack_modules__ = {"./src/a.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__, {A: () => A,});var _b_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/b.js");var A = function A() {};},"./src/b.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__);var _a_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/a.js");(0, _a_js__WEBPACK_IMPORTED_MODULE_0__.A)();},};var __webpack_module_cache__ = {};function __webpack_require__(moduleId) {var cachedModule = __webpack_module_cache__[moduleId];if (cachedModule !== undefined) {return cachedModule.exports;}var module = (__webpack_module_cache__[moduleId] = {exports: {},});__webpack_modules__[moduleId](module, module.exports, __webpack_require__);return module.exports;}(() => {__webpack_require__.d = (exports, definition) => {for (var key in definition) {if (__webpack_require__.o(definition, key) &&!__webpack_require__.o(exports, key)) {Object.defineProperty(exports, key, {enumerable: true,get: definition[key],});}}};})();(() => {__webpack_require__.o = (obj, prop) =>Object.prototype.hasOwnProperty.call(obj, prop);})();(() => {__webpack_require__.r = (exports) => {if (typeof Symbol !== "undefined" && Symbol.toStringTag) {Object.defineProperty(exports, Symbol.toStringTag, {value: "Module",});}Object.defineProperty(exports, "__esModule", { value: true });};})();var __webpack_exports__ = {};__webpack_require__.r(__webpack_exports__);var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
})();
每個模塊的代碼會被放到一個對象
var __webpack_modules__ = {[moduleId] : 模塊代碼
}
var __webpack_modules__ = {"./src/a.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__); // 標(biāo)記模塊為ES模塊__webpack_require__.d(__webpack_exports__, {A: () => A, // getter});var _b_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/b.js");var A = function A() {};},"./src/b.js": (__unused_webpack_module,__webpack_exports__,__webpack_require__) => {__webpack_require__.r(__webpack_exports__);var _a_js__WEBPACK_IMPORTED_MODULE_0__ =__webpack_require__("./src/a.js");(0, _a_js__WEBPACK_IMPORTED_MODULE_0__.A)();},};
webpack自定義require導(dǎo)入函數(shù)
function __webpack_require__(moduleId) {var cachedModule = __webpack_module_cache__[moduleId];if (cachedModule !== undefined) {return cachedModule.exports;}var module = (__webpack_module_cache__[moduleId] = {exports: {},});__webpack_modules__[moduleId](module, module.exports, __webpack_require__);return module.exports;
}
跟commonjs規(guī)范類似
- 查看緩存是否有模塊導(dǎo)出結(jié)果,如果模塊執(zhí)行過了,返回模塊導(dǎo)出結(jié)果
- 在執(zhí)行模塊代碼之前,先創(chuàng)建模塊導(dǎo)出對象module.exports
- 將模塊導(dǎo)出對象傳入執(zhí)行模塊代碼
__webpack_require__.d // 定義模塊導(dǎo)出屬性
__webpack_require__.o // 檢查模塊導(dǎo)出對象是否具有某個屬性
__webpack_require__.r // 標(biāo)記模塊為ES模塊
模塊代碼執(zhí)行前會先進行
- 將模塊導(dǎo)出對象標(biāo)記ES模塊
- 如果模塊有導(dǎo)出內(nèi)容,會將這些內(nèi)容定義到模塊導(dǎo)出對象
代碼執(zhí)行流程
模塊A執(zhí)行
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {A: () => A, // 定義getter
});
var _b_js__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/b.js"); // 執(zhí)行到這里 會暫停a模塊代碼執(zhí)行,執(zhí)行b模塊var A = function A() {};
moduleA 定義了一個A屬性,A屬性是一個存取器屬性,有g(shù)etter,getter就是返回真正導(dǎo)出的A。
執(zhí)行b模塊時,()=>A,這里返回的A還是undefined。
執(zhí)行b模塊
__webpack_require__.r(__webpack_exports__);
var _a_js__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/a.js");(0, _a_js__WEBPACK_IMPORTED_MODULE_0__.A)();
跟Commonjs的問題一樣,模塊A還沒有執(zhí)行完,A還沒有賦值,所以A這里是undefined,不能作為函數(shù)調(diào)用。
這里和commonjs還是有些區(qū)別
打包結(jié)果中模塊代碼執(zhí)行前會去先定義導(dǎo)出屬性,為屬性設(shè)置一個getter,因此在代碼模塊執(zhí)行前這些導(dǎo)出屬性就已經(jīng)在導(dǎo)出對象中有g(shù)etter。
這里因為配置babel,打包結(jié)果會把const轉(zhuǎn)成var,所以變量聲明提升了,如果是const就會變成變量在聲明前使用。
結(jié)論
項目會形成循環(huán)依賴實際開發(fā)中很難避免,有可能引入了某個模塊就會導(dǎo)致形成依賴鏈路。
形成循環(huán)依賴鏈路并不一定會報錯,但是在執(zhí)行到對應(yīng)模塊時,之前模塊因為導(dǎo)入其他包,模塊代碼還沒完全執(zhí)行完,內(nèi)容還沒完全導(dǎo)出,就有可能導(dǎo)致報錯。
其實導(dǎo)致報錯還好,因為可以提前在本地就感知到處理,但是如果你只是定義了一個變量,那么這個變量可能是在你還沒有賦值的時候,就引用了,所以其實模塊導(dǎo)出的變量并不是一定可信的。
其實在遇到函數(shù)調(diào)用報錯時可以通過把一些函數(shù)表達式改成函數(shù)聲明就好,因為打包結(jié)果模塊的執(zhí)行其實是執(zhí)行一個函數(shù),在執(zhí)行前會有函數(shù)聲明提升,但盡量不要用這種規(guī)范來處理,因為很可能會遇到更多坑。
其實有模塊循環(huán)依賴后還報錯,本身就是這條依賴鏈路有問題,應(yīng)該找到不合理的地方解決,而不是去規(guī)避。用函數(shù)聲明解決一些問題,反倒會留下一些坑,可能某些環(huán)境的值原本因為循環(huán)依賴導(dǎo)致引用時是undefined,但是碰巧你用函數(shù)聲明避免了一些報錯,導(dǎo)致埋了一個坑。
有一些工具可以分析項目中的循環(huán)鏈路,eslint也有相應(yīng)的配置。
至于如何找到循環(huán)依賴的不合理地方就憑經(jīng)驗吧,這里就不展開了,畢竟是個人觀點。