一文彻底弄懂 “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.jssrc/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.exportsexports 导出模块的时候,输出的是值的拷贝。

EsModule 模块输出的是值的引用

EsModule 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

下面我们用 WebpackEsModule 处理方式解析一下 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.jsmoduleId1esmodule/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)的区别与联系

GIS风暴一文彻底弄懂数字地形(DEMDOMTDOMDSM)的区别与联系

一文彻底弄懂Linux-Shell编程

一文彻底弄懂Linux-Shell编程

一文彻底弄懂Linux-Shell编程