一文彻底弄懂 “CommonJs” 与 “EsModule” 区别
Posted vv_小虫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文彻底弄懂 “CommonJs” 与 “EsModule” 区别相关的知识,希望对你有一定的参考价值。
简介
文章中就不具体解释什么是 “CommonJs” 与 “EsModule” 了,我们先简单的描述一下 “CommonJs” 与 “EsModule” 的区别。
它们有三个重大差异。
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的
require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。
因为在 Webpack
中是支持这两种模块加载方式的,所以接下来我们就通过 Webpack
并结合 Demo
来具体解释一下这三个差异。
对 Webpack
不熟的童鞋可以看一下我的这篇文章:来和 webpack 谈场恋爱吧
准备工作
初始化
我们先创建一个 module-demo
目录,然后执行 npm
的初始化:
mkdir module-demo && cd module-demo && npm init -y
安装 Webpack
在 module-demo
目录下执行以下命令:
npm install webpack webpack-cli --registry=https://registry.npm.taobao.org
配置 Webpack
在 module-demo
项目根目录下创建一个 webpack.config.js
文件:
touch webpack.config.js
然后将以下内容写入到 module-demo/webpack.config.js
文件:
module.exports =
mode: "none", // 为了测试方便,直接改成 node 模式
output:
pathinfo: true, // 添加 module 的 path 信息
,
devtool: false, // 去掉 source-map 信息
Webpack
的配置就不详细介绍了,感谢的小伙伴可以看一下我的这篇文章:来和 webpack 谈场恋爱吧
创建入口文件
在 module-demo
项目根目录下创建一个 src
目录:
mkdir src
并在 module-demo/src
目录下创建一个 index.js
文件,用来作为我们 webpack
打包的入口文件:
touch ./src/index.js
测试
我们可以试着在 module-demo
项目根目录下执行以下命令进行 webpack
打包命令测试:
npx webpack
可以看到,在项目的 module-demo/dist
目录下生成了一个 main.js
文件,main.js
文件就是我们通过 webpack
打包出来的资源文件。
ok,到这我们的准备工作就结束啦!
差别(一)
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块输出的是值的拷贝
我们在 module-demo/src
目录下创建一个 commonjs
目录,用来存放 commonjs
模块:
mkdir ./src/commonjs
然后在 src/commonjs
目录下创建一个 lib.js
文件:
touch ./src/commonjs/lib.js
接着将以下内容写入到 src/commonjs/lib.js
文件:
// lib.js
var counter = 3;
function incCounter()
counter++;
module.exports =
counter: counter,
incCounter: incCounter,
;
上面代码输出内部变量counter
和改写这个变量的内部方法incCounter
。
然后我们在src/index.js
里面加载这个模块:
// main.js
var mod = require('./commonjs/lib');
console.log(mod.counter); // 打印 counter 值
mod.incCounter();
console.log(mod.counter); // 打印 counter 值
最后我们在 module-demo
根目录下执行 webpack
打包命令:
npx webpack
等打包完成后,我们运行一下 webpack
打包过后的 dist/main.js
文件:
node ./dist/main.js
可以看到,两次 console.log
打印的都是 3
,我们调用了 incCounter
方法后,counter
并没有改变,这是为什么呢?
我们看一下打包过后的 dist/main.js
文件:
/******/
(() => // webpackBootstrap
/******/
var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/*!*****************************!*\\
!*** ./src/commonjs/lib.js ***!
\\*****************************/
/***/ ((module) =>
// lib.js
var counter = 3;
function incCounter()
counter++;
module.exports =
counter: counter,
incCounter: incCounter,
;
/***/
)
/******/]);
/************************************************************************/
/******/ // 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 ***!
\\**********************/
// main.js
var mod = __webpack_require__(/*! ./commonjs/lib */ 1);
console.log(mod.counter); // 打印 counter 值
mod.incCounter();
console.log(mod.counter); // 打印 counter 值
)();
/******/
)()
;
当我们执行 node ./dist/main.js
命令的时候,最先执行了这段代码:
(() =>
/*!**********************!*\\
!*** ./src/index.js ***!
\\**********************/
// main.js
var mod = __webpack_require__(/*! ./commonjs/lib */ 1);
console.log(mod.counter); // 打印 counter 值
mod.incCounter();
console.log(mod.counter); // 打印 counter 值
)();
也就是我们的入口文件 src/index.js
文件:
// index.js
var mod = require('./commonjs/lib');
console.log(mod.counter); // 打印 counter 值
mod.incCounter();
console.log(mod.counter); // 打印 counter 值
当我们用 require
导入 ./commonjs/lib
模块的时候,webpack 最后会编译成这样:
// index.js
var mod = require('./commonjs/lib'); // 编译前
// dist/main.js
var mod = __webpack_require__(/*! ./commonjs/lib */ 1); // 编译后
ok,我们看一下dist/main.js
文件中的 __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;
可以看到,在 __webpack_require__
方法中,首先是去缓存中找这个模块,如果缓存中没有该模块的话,就会新创建一个模块,然后执行这个模块,最后导出这个模块。
我们重点看一下执行该模块的代码:
// 执行该模块
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
__webpack_modules__
其实就是一个模块数组,用来存放我们源文件中的所有模块信息:
var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/*!*****************************!*\\
!*** ./src/commonjs/lib.js ***!
\\*****************************/
/***/ ((module) =>
// lib.js
var counter = 3;
function incCounter()
counter++;
module.exports =
counter: counter,
incCounter: incCounter,
;
/***/
)
/******/]);
可以看到,我们当前 demo
中有两个模块 src/index.js
与 src/commonjs/lib.js
,因为 src/index.js
没有导出任何模块信息,所以 __webpack_modules__
数组的第一个子项为 undefined
,当我们执行:
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
代码的时候,我们传递进来的是 1
:
// dist/main.js
var mod = __webpack_require__(/*! ./commonjs/lib */ 1); // 编译后
所以最后会执行 __webpack_modules__
数组的第 1
项内容,也就是我们的 src/commonjs/lib.js
模块:
var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/*!*****************************!*\\
!*** ./src/commonjs/lib.js ***!
\\*****************************/
/***/ ((module) =>
// lib.js
var counter = 3;
function incCounter()
counter++;
module.exports =
counter: counter,
incCounter: incCounter,
;
/***/
)
/******/]);
可以看到,src/commonjs/lib.js
被编译成了这样:
((module) =>
// lib.js
var counter = 3;
function incCounter()
counter++;
module.exports =
counter: counter,
incCounter: incCounter,
;
)
所以我们在 src/index.js
中引入的 ./commonjs/lib
模块最后获取的是 module.exports
对象:
counter: counter,
incCounter: incCounter,
直接把 var counter = 3;
的值给了 module.exports
对象,所以即使我们后面又调用了 incCounter
方法,但是改变的是 var counter = 3;
这个变量的值,而我们的 module.exports
对象的 counter
值没有改变,所以最后两次输出的都是 3
,所以 CommonJs
中,当我们用 module.exports
或 exports
导出模块的时候,输出的是值的拷贝。
EsModule 模块输出的是值的引用
EsModule 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
下面我们用 Webpack
对 EsModule
处理方式解析一下 EsModule
的原理。
我们先在 src
目录下创建一个 esmodule
目录:
mkdir ./src/esmodule
然后在 src/esmodule
目录下也创建一个 lib.js
文件:
touch ./src/esmodule/lib.js
接着用 EsModule
的方式重写一下 src/commonjs/lib.js
文件:
// lib.js
export var counter = 3;
export function incCounter()
counter++;
最后我们改一下入口文件 src/index.js
文件:
// main.js
// var mod = require('./commonjs/lib');
import * as mod from "./esmodule/lib";
console.log(mod.counter); // 打印 counter 值
mod.incCounter();
console.log(mod.counter); // 打印 counter 值
我们执行 webpack
打包操作:
npx webpack
同样,等打包完毕后运行 dist/main.js
文件:
node ./dist/main.js
可以看到,第一次输出的是 3
,然后执行完 mod.incCounter()
后,mod.counter
变成了 4
,这是为什么呢?
我们打开 dist/main.js
源码看看:
/******/
(() => // webpackBootstrap
/******/
"use strict";
/******/
var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/*!*****************************!*\\
!*** ./src/esmodule/lib.js ***!
\\*****************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) =>
__webpack_require__.r(__webpack_exports__);
/* harmony export */
__webpack_require__.d(__webpack_exports__,
/* harmony export */ "counter": () => (/* binding */ counter),
/* harmony export */ "incCounter": () => (/* binding */ incCounter)
/* harmony export */
);
// lib.js
var counter = 3;
function incCounter()
counter++;
/***/
)
/******/]);
/************************************************************************/
/******/ // 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/runtime/define property getters */
/******/
(() =>
/******/ // define getter functions for harmony 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/runtime/hasOwnProperty shorthand */
/******/
(() =>
/******/
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/
)();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/
(() =>
/******/ // 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__ = ;
// 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__.r(__webpack_exports__);
/* harmony import */
var _esmodule_lib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./esmodule/lib */ 1);
// main.js
// var mod = require('./commonjs/lib');
console.log(_esmodule_lib__WEBPACK_IMPORTED_MODULE_0__.counter); // 打印 counter 值
_esmodule_lib__WEBPACK_IMPORTED_MODULE_0__.incCounter();
console.log(_esmodule_lib__WEBPACK_IMPORTED_MODULE_0__.counter); // 打印 counter 值
)();
/******/
)()
;
我们先看一下入口文件 src/index.js
:
(() =>
/*!**********************!*\\
!*** ./src/index.js ***!
\\**********************/
__webpack_require__.r(__webpack_exports__);
// 加载 esmodule/lib.js 模块
var _esmodule_lib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log(_esmodule_lib__WEBPACK_IMPORTED_MODULE_0__.counter); // 打印 counter 值
// 执行 esmodule/lib.js 模块的 incCounter 方法
_esmodule_lib__WEBPACK_IMPORTED_MODULE_0__.incCounter();
console.log(_esmodule_lib__WEBPACK_IMPORTED_MODULE_0__.counter); // 打印 counter 值
)();
可以看到,首先是去用 __webpack_require__
方法去加载了 esmodule/lib.js
模块,esmodule/lib.js
的 moduleId
为 1
,esmodule/lib.js
被转换成了 __webpack_modules__
数组的第一项:
/*!*****************************!*\\
!*** ./src/esmodule/lib.js ***!
\\*****************************/
((__unused_webpack_module, __webpack_exports__, __webpack_require__) =>
__webpack_require__.r(__webpack_exports__);
// 定义模块的输出
__webpack_require__.d(__webpack_exports__,
"counter": () => (/* binding */ counter), // 返回 counter 的引用
"incCounter": () => (/* binding */ incCounter) // 返回 incCounter 的引用
);
// lib.js
var counter = 3;
function incCounter()
counter++;
)
可以看到,这一次不再是直接导出一个 module.exports
对象了,这一次是利用 __webpack_require__.d
方法定义了模块的输出:
// 定义模块的输出
__webpack_require__.d(__webpack_exports__,
"counter": () => (/* binding */ counter), // 返回 counter 的引用
"incCounter"[转]js模块化编程之彻底弄懂CommonJS和AMD/CMD!
GIS风暴一文彻底弄懂数字地形(DEMDOMTDOMDSM)的区别与联系