給一個(gè)裝修公司怎么做網(wǎng)站今日新聞播報(bào)
模塊循環(huán)依賴問題
在項(xiàng)目比較小的時(shí)候可能不怎么會(huì)遇到這個(gè)問題,但項(xiàng)目一旦有一定的體量后就可能會(huì)遇到了。
我之前做項(xiàng)目時(shí)就遇到這個(gè)問題,也是總結(jié)一篇文章。
比如這種類型的報(bào)錯(cuò)
commonjs存在的問題
先講一下commonjs存在的問題。
CommonJS模塊采用深度優(yōu)先遍歷,并且是加載時(shí)執(zhí)行,即腳本代碼在require時(shí)就全部執(zhí)行。一旦出現(xiàn)某個(gè)模塊被“循環(huán)加載”,就只輸出已經(jīng)執(zhí)行的部分,沒有執(zhí)行的部分不會(huì)輸出。
舉例子
// a.js
require("./b.js");
exports.a = function () {};// b.js
const { a } = require("./a");
a();// index.js
require("./a.js");
執(zhí)行index.js
結(jié)果:報(bào)錯(cuò)a is not function
執(zhí)行流程
1 導(dǎo)入a.js
require("a.js")
// 此時(shí)moduleCache
moduleCache = {moduleA : {}
}
2 執(zhí)行a.js為moduleA添加屬性,發(fā)現(xiàn)第一行導(dǎo)入b.js,模塊a還沒執(zhí)行完,執(zhí)行b.js
require("./b.js");
// 此時(shí)moduleCache
moduleCache = {moduleA : {},moduleB : {}
}
3 執(zhí)行b.js,發(fā)現(xiàn)導(dǎo)入a.js,此時(shí)moduleCache有moduleA,不會(huì)重復(fù)執(zhí)行模塊a的代碼,會(huì)直接用moduleCache中模塊a已經(jīng)導(dǎo)出的內(nèi)容。
const { a } = require("./a");
等價(jià)于
const {a} = moduleCache.moduleA
因?yàn)榇藭r(shí)模塊a的內(nèi)容還未完全執(zhí)行完,所以解構(gòu)的變量a是undefined,還不是function,所以報(bào)錯(cuò)。
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");
})();
每個(gè)模塊的代碼會(huì)被放到一個(gè)對(duì)象
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)出對(duì)象module.exports
- 將模塊導(dǎo)出對(duì)象傳入執(zhí)行模塊代碼
__webpack_require__.d // 定義模塊導(dǎo)出屬性
__webpack_require__.o // 檢查模塊導(dǎo)出對(duì)象是否具有某個(gè)屬性
__webpack_require__.r // 標(biāo)記模塊為ES模塊
模塊代碼執(zhí)行前會(huì)先進(jìn)行
- 將模塊導(dǎo)出對(duì)象標(biāo)記ES模塊
- 如果模塊有導(dǎo)出內(nèi)容,會(huì)將這些內(nèi)容定義到模塊導(dǎo)出對(duì)象
代碼執(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í)行到這里 會(huì)暫停a模塊代碼執(zhí)行,執(zhí)行b模塊var A = function A() {};
moduleA 定義了一個(gè)A屬性,A屬性是一個(gè)存取器屬性,有g(shù)etter,getter就是返回真正導(dǎo)出的A。
執(zhí)行b模塊時(shí),()=>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í)行前會(huì)去先定義導(dǎo)出屬性,為屬性設(shè)置一個(gè)getter,因此在代碼模塊執(zhí)行前這些導(dǎo)出屬性就已經(jīng)在導(dǎo)出對(duì)象中有g(shù)etter。
這里因?yàn)榕渲胋abel,打包結(jié)果會(huì)把const轉(zhuǎn)成var,所以變量聲明提升了,如果是const就會(huì)變成變量在聲明前使用。
結(jié)論
項(xiàng)目會(huì)形成循環(huán)依賴實(shí)際開發(fā)中很難避免,有可能引入了某個(gè)模塊就會(huì)導(dǎo)致形成依賴鏈路。
形成循環(huán)依賴鏈路并不一定會(huì)報(bào)錯(cuò),但是在執(zhí)行到對(duì)應(yīng)模塊時(shí),之前模塊因?yàn)閷?dǎo)入其他包,模塊代碼還沒完全執(zhí)行完,內(nèi)容還沒完全導(dǎo)出,就有可能導(dǎo)致報(bào)錯(cuò)。
其實(shí)導(dǎo)致報(bào)錯(cuò)還好,因?yàn)榭梢蕴崆霸诒镜鼐透兄教幚?#xff0c;但是如果你只是定義了一個(gè)變量,那么這個(gè)變量可能是在你還沒有賦值的時(shí)候,就引用了,所以其實(shí)模塊導(dǎo)出的變量并不是一定可信的。
其實(shí)在遇到函數(shù)調(diào)用報(bào)錯(cuò)時(shí)可以通過把一些函數(shù)表達(dá)式改成函數(shù)聲明就好,因?yàn)榇虬Y(jié)果模塊的執(zhí)行其實(shí)是執(zhí)行一個(gè)函數(shù),在執(zhí)行前會(huì)有函數(shù)聲明提升,但盡量不要用這種規(guī)范來處理,因?yàn)楹芸赡軙?huì)遇到更多坑。
其實(shí)有模塊循環(huán)依賴后還報(bào)錯(cuò),本身就是這條依賴鏈路有問題,應(yīng)該找到不合理的地方解決,而不是去規(guī)避。用函數(shù)聲明解決一些問題,反倒會(huì)留下一些坑,可能某些環(huán)境的值原本因?yàn)檠h(huán)依賴導(dǎo)致引用時(shí)是undefined,但是碰巧你用函數(shù)聲明避免了一些報(bào)錯(cuò),導(dǎo)致埋了一個(gè)坑。
有一些工具可以分析項(xiàng)目中的循環(huán)鏈路,eslint也有相應(yīng)的配置。
至于如何找到循環(huán)依賴的不合理地方就憑經(jīng)驗(yàn)吧,這里就不展開了,畢竟是個(gè)人觀點(diǎn)。