赞
踩
如果没有模块化,很容易出现全局污染和依赖管理混乱的问题。其中,全局污染是指script 内部的变量是可以相互污染的,比如两个脚本中定义了重名的变量,那么这个变量就会被污染。依赖管理混乱是指当多个js脚本之间存在依赖关系,那么只有下层 js 能调用上层 js 的方法,但是上层 js 无法调用下层 js 的方法。为解决上述这两个问题,就出现了模块化。模块化两个重要的解决方案就是CommonJS与和ES Modules。
我们知道CommonJS采用require导入,exports或module.exports导出。在 Commonjs 模块中,会形成一个包装函数,我们的代码将作为包装函数的执行上下文,使用的 require ,exports ,module 本质上是通过形参的方式传递到包装函数中的。在编译阶段会形成这个包装函数,在模块加载时执行这个包装函数。
当模块有相互依赖关系时,我们来看一下CommonJS是如何处理的
main.js
//main1
console.log("main导入a模块");
const a = require('./a');
//-----------------------------
//main2
console.log("main输出a模块----");
console.log(a)
a.js
//a1
console.log('a模块--导入b之前')
const b = require('./b');
//----------------------------
//a2
console.log("a输出B模块----");
console.log(b)
console.log('a模块--导入b之后')
module.exports = '我是A模块'
b.js
//b1
console.log('b模块--导入a之前')
const a = require('./a');
//b2
//----------------------------
console.log("B输出a模块----");
console.log(a)
console.log('b模块--导入a之后')
module.exports = '我是B模块'
运行main.js,输出如下:
模块a、b会被require分为上下两部分,在main.js中遇到require后,加载并执行a模块,a.js中遇到require,加载并执行b模块,b.js中又遇到require(‘./a’)发生了循环引用,因为模块a已经被加载过,直接从缓存中取,由于取出的a模块还没执行完,因此输出a是个空对象。b.js执行完后会返回到刚刚a.js的地方继续执行。三个文件的执行流程为:main1->a1->b1->b2->a2->main2。因此说CommonJS 模块在执行阶段分析模块依赖,采用深度优先遍历(depth-first traversal),执行顺序是父 -> 子 -> 父。由于存在一个缓存机制,所以可以解决循环引用的问题。
在 Es Module 中用 export 用来导出模块,import 用来导入模块。CommonJS 模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父。
CommonJS 输出的是一个值的拷贝;ES Modules 生成一个引用,等到真的需要用到时,再到模块里面去取值,模块里面的变量,绑定其所在的模块。
CommonJS :
count.js
let num = 1
function add(){
return num++
}
module.exports = {num,add}
index.js
const {num,add} = require('./count')
console.log(num) //1
add()
console.log(num) //1
module.exports = {num,add}相当于module.exports = {num:num,add:add},num为基本数据类型,内存地址指向n1,当赋值给module.exports[‘num’] 时,内存地址已指向n2,之后对module.exports[‘num’] 的任何操作已与内存地址指向n1的num无关。
add函数为引用数据类型, 当add赋值给 module.exports[‘add’] 时,只进行了指针的复制,其内存地址依旧指向同一块数据。
通过webpack打包后的js文件也能看出两者的区别,下面先看一下CommonJS打包后的文件:
/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/count.js": /***/ ((module) => { eval("let num = 1\nfunction add(count){\n return count++\n}\nmodule.exports = {num,add}\n\n//# sourceURL=webpack:///./src/count.js?"); /***/ }), /***/ "./src/index.js": /***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { eval("const {num,add} = __webpack_require__(/*! ./count */ \"./src/count.js\")\nconsole.log(num)\nadd(num)\nconsole.log(num)\n\n\n//# sourceURL=webpack:///./src/index.js?"); /***/ }) /******/ }); /******/ // 缓存 /******/ var __webpack_module_cache__ = {}; /******/ // webpack封装的require函数 /******/ 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; /******/ } /******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); /******/ })() ;
ES Modules:
count.js
let num = 1
function add(count){
return count++
}
export {num,add}
index.js
import {num,add} from './count'
console.log(num) //1
add(num)
console.log(num) //2
在webpack打包后的js文件中可以发现,ES Modules是将值作为箭头函数的返回值,再把箭头函数赋值给导出对象,重点关注__webpack_require__.d方法。
/******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./src/count.js": /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"add\": () => (/* binding */ add),\n/* harmony export */ \"num\": () => (/* binding */ num)\n/* harmony export */ });\nlet num = 1\nfunction add(count){\n return count++\n}\n\n\n//# sourceURL=webpack:///./src/count.js?"); /***/ }), /***/ "./src/index.js": /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _count__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./count */ \"./src/count.js\");\n\nconsole.log(_count__WEBPACK_IMPORTED_MODULE_0__.num)\n;(0,_count__WEBPACK_IMPORTED_MODULE_0__.add)(_count__WEBPACK_IMPORTED_MODULE_0__.num)\nconsole.log(_count__WEBPACK_IMPORTED_MODULE_0__.num)\n\n\n//# sourceURL=webpack:///./src/index.js?"); /******/ var __webpack_module_cache__ = {}; /******/ // 同上 /******/ function __webpack_require__(moduleId) {} /******/ //__webpack_require__.d方法给exports添加导出变量,使用exports.key时会调用getter方法 /******/ (() => { /******/ __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方法判断prop是否已经挂载到obj上 /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ //__webpack_require__.r方法为当前打包的模块做个标记,表明这是个符合ES Modules规范导出的模块 /******/ (() => { /******/ // define __esModule on exports /******/ __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__("./src/index.js"); /******/ })() ;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。