模块化:CommonJS与ES Modules详解

一、模块化的目的

如果没有模块化,很容易出现全局污染依赖管理混乱的问题。其中,全局污染是指script 内部的变量是可以相互污染的,比如两个脚本中定义了重名的变量,那么这个变量就会被污染。依赖管理混乱是指当多个js脚本之间存在依赖关系,那么只有下层 js 能调用上层 js 的方法,但是上层 js 无法调用下层 js 的方法。为解决上述这两个问题,就出现了模块化。模块化两个重要的解决方案就是CommonJS与和ES Modules。

二、CommonJS详解

我们知道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 Modules详解

在 Es Module 中用 export 用来导出模块,import 用来导入模块。CommonJS 模块同步加载并执行模块文件,ES6 模块提前加载并执行模块文件,ES6 模块在预处理阶段分析模块依赖,在执行阶段执行模块,两个阶段都采用深度优先遍历,执行顺序是子 -> 父。

四、CommonJS与ES Modules区别

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");
/******/ })()
;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值