起因
element-ui的popper组件相关的层级,是使用popup-manager来统一管理的。
之前试图在自己的组件里导入并使用element-ui的popup-manager,但是层级老是和element-ui组件的层级冲突,看了下源码,竟意外发现,使用popup-manager时,是调用其内部方法nextZIndex修改导出的PopupManager.zIndex,来实现不同popper的正确层级:
/* PopupManager */
const PopupManager = {
nextZIndex: function() {
return PopupManager.zIndex++;
},
}
export default PopupManager;
/* 其他地方使用 */
import PopupManager from 'element-ui/src/utils/popup/popup-manager';
PopupManager.nextZIndex()
// 或者直接在外部修改
PopupManager.zIndex = zIndex;
PopupManager这种管理层级的方法,有点类似于“全局变量”,即只要是导入该模块的文件,实际是共享一个zIndex变量,以达到zIndex的正确累加。经过构建工具的打包之后,这些使用PopupManager.zIndex的模块,实际上是使用了一个变量。而我再导入的popup-manager,显然和element-ui使用的popup-manager不是一个对象。
但自己之前似乎从未有过这种作法,即直接修改其他模块的导出。同时不禁有个疑惑,这种做法真的有效吗,构建工具到底是怎么处理模块化的?干脆直接看下webpack打包后的代码吧。
webpack处理模块化示例
准备工作
首先,准备一个webpack项目,内容很简单,src/index.js是打包入口,src/modules目录下有三个模块a、b、c
a模块导出一个变量name
b模块中导入并尝试修改a模块导出的name
c模块导入a模块,检测a中的name有没有被修改
最后,在index.js导入b、c
打包并执行
没有什么意外,模块导出的变量,确实可以在其他模块中修改,a.name从原先的'jyj',被修改为了'b'
为啥会这样
直接看webpack打包后的代码:
核心:__webpack_require__方法,即require方法,调用该方法时,会优先从__webpack_module_cache__中返回已缓存的模块,这也提示我们,相同模块的代码,只会执行一次。
// The module cache
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = __webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {}
};
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
如果引入的模块没有加载过,那么webpack会从__webpack_modules__对象中取出需要导入的模块函数,并且向该函数的上下文中传入module, module.exports, __webpack_require__三个变量,执行对应模块的代码。这三个变量对应了我们在模块中使用的module, module.exports, require方法,模块内部会使用module.exports = {}这样的语法,将要导出的内容挂载到module上。
同时可以发现,所谓的模块,就是一个函数。
var __webpack_modules__ = ({
/***/ "./src/modules/a.js": ((module) => {
const name = 'jyj'
console.log('a加载了');
module.exports = {
name
}
}),
/***/ "./src/modules/b.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
const a = __webpack_require__(/*! ./a.js */ "./src/modules/a.js")
a.name = "b"
}),
/***/ "./src/modules/c.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
const a = __webpack_require__(/*! ./a.js */ "./src/modules/a.js")
console.log(a.name, 'a.name');
})
});
现在我们已经理解了webpack处理模块化的基本原理。再来看入口index.js进行了哪些处理。
由于index.js引入了b、c模块,webpack调用__webpack_require__,引入这两个模块,
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
__webpack_require__(/*! ./modules/b.js */ "./src/modules/b.js")
__webpack_require__(/*! ./modules/c.js */ "./src/modules/c.js")
})();
这个时候,b、c并未加载,所以webpack会去加载b、c,即__webpack_modules__[moduleId](module, module.exports, __webpack_require__)。
由于先导入b模块,所以先执行b.js。b又导入了a模块,同理,webpack又去加载执行a模块,加载完成后,__webpack_module_cache__中已经有了a模块导出的对象module.exports。__webpack_require__(/*! ./a.js */ "./src/modules/a.js")就是a模块的module.exports,即{name: 'jyj'}。
b.js中将name修改为'b',a模块的module.exports此时已经为{name: 'b'},
/***/ "./src/modules/b.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
const a = __webpack_require__(/*! ./a.js */ "./src/modules/a.js")
a.name = "b"
}),
紧接着,加载c模块。c.js中引入了a模块,此时a模块已在缓存中,再次加载直接返回缓存中的'./src/modules/a.js'对应值的exports,即{exports: {name: 'b'}}.exports。接着遇到console.log打印输出。
/***/ "./src/modules/c.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
const a = __webpack_require__(/*! ./a.js */ "./src/modules/a.js")
console.log(a.name, 'a.name');
})
总结
以webpack处理commonjs模块化为例,webpack将已加载过的模块对象存放在__webpack_module_cache__中。键名可以理解为模块名,键值可以简单理解为就是存储模块导出的对象。
未加载的模块存放在__webpack_modules__中。
每个模块代码其实是在一个函数里,webpack向该函数作用域内注入了module、module.exports、require这些对象,以供模块导入导出。导出的内容会挂载到对应的module.exports里。导入就是导入的module.exports对象。
由于本质是利用函数进行作用域隔离,对象进行变量共享。所以,修改导入模块的成员变量,一定会对使用该模块的地方产生影响。
最后贴出完整的webpack打包后代码
/******/ (() => { // webpackBootstrap
var __webpack_modules__ = ({
/***/ "./src/modules/a.js": ((module) => {
const name = 'jyj'
console.log('a加载了');
module.exports = {
name
}
}),
/***/ "./src/modules/b.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
const a = __webpack_require__(/*! ./a.js */ "./src/modules/a.js")
a.name = "b"
}),
/***/ "./src/modules/c.js": ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
const a = __webpack_require__(/*! ./a.js */ "./src/modules/a.js")
console.log(a.name, 'a.name');
})
});
/************************************************************************/
// The module cache
var __webpack_module_cache__ = {};
// The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
__webpack_require__(/*! ./modules/b.js */ "./src/modules/b.js")
__webpack_require__(/*! ./modules/c.js */ "./src/modules/c.js")
})();
/******/ })()
;
//# sourceMappingURL=index.bundle.js.map