Commonjs 的本质

334 阅读3分钟

一、前言

Commonjs 是nodejs 提出的默认的模块化解决方案:每个文件就是一个模块,文件的最顶层就处于一个函数作用域中,函数接受了一些参数(五个),这五个参数在模块中可见。在函数作用域(文件作用做)中定义的变量、 函数、类,都是私有的,对其他文件不可见。

二、分析commonjs 的导出

代码如下:

this.a = 1;
exports.b = 2;
exports = {
    c:3,
}
modules.exports = {
    d:4,
}

this.e = 5;
exports.f = 6;
modules.exports.e = 7;

请问,以上代码的导出结果是啥?

我们知道,一个模块的导入,依赖的是require函数, 就像这样: const module1 = require('./module1.js') , 要从原理上解释清楚以上模块的导出结果,就需要分析require函数执行的时候都干了些啥???

下面给出require 函数的伪代码实现。

三、Require 函数的原理(伪代码)

先看代码再解释:

// 不是重点省略实现
function getModuleId(path){}
// 不是重点省略实现
function getDirname(path){}
// 缓存
var cache = {}
function require (path){
    // 1. 根据传递的模块路径找到对应的绝对路径,作为模块的id
    var moduleId = getModuleId(path)
    // 2. 判断是否是第一次执行(是否有缓存),如果有缓存,直接返回缓存,没有缓存就执行一个函数得到结果,然后把结果缓存并导出
    if(!!cache[moduleId]){
        return cache[moduleId]
    }
    // 3. require真正需要运行的函数。
    function _require(exports,require,module,__filename,__dirname){
        // 通过前面的的id, 获取模块的代码,放在这里,等待调用
        // 因此,commonjs 模块的全局上下文处于一个函数环境,函数接收五个参数,这也是为什么commonjs 全局环境具有五个参数的原因。

        /* 假设这里已经根据path的绝对路劲拿到了 module1.js 的代码,就会放在这里
        * this.a = 1;
        * module.exports = {
        *   b:2
        * }
        * console.log(__filename)
        * console.log(__dirname)
        * console.log(arguments.length)
        *
        * // 等等,就是一个普通的函数。。。。
        * */
    }

    // 准备_require函数需要的参数
    var module = {
        exports:{}
    }
    var exports = module.exports;
    // 文件的绝对路径
    var __filename = moduleId
    // 文件所在目录的绝对路径
    var __dirname = getDirname(__filename);

    // 执行_require 函数,给模块赋值,使用call 绑定函数中的this 为exports
    _require.call(exports,exports,require,module,__filename,__dirname);
    // 缓存module
    cache[moduleId] = exports;
    // 返回赋值好的模块对象
    return module.exports;
}

三、解释require函数

require(path) 主要做了以下几件事:

  1. 根据传进来的饿path获取到文件的绝对路径作为模块id,记为 moduleId
  2. 根据moduleId判断是否有缓存,有缓存就返回,没有继续执行下面的饿步骤。
  3. 准备一个辅助函数_require(exports,require,module,__filename,__dirname),根据moduleId 获取到模块定义的代码,然后把它放在这个函数体中。
  4. 准备好需要的五个参数。exports,module,__filename,__dirname
    var module = {
       exports:{}
    }
    var exports = module.exports;
    // 文件的绝对路径
    var __filename = moduleId
    // 文件所在目录的绝对路径
    var __dirname = getDirname(__filename);
    

    可以看到 expoorts 就指向 module.exports,他们是同一片内存空间。

  5. 通过FUnction.prototype.call调用该函数,可以将将函数的this绑定到exports,函数执行过程中会给module.exports 赋值。

    因此 this、exports、module.exports指向同一片内存空间。

  6. 缓存exports.
  7. 返回exports.

四、回答前面的问题(分析commonjs 的导出)

exports = {
    d:4,
    e:7,
    f:6
}

认真看了前面这里肯定不用解释。

码字不易,帮到你就给个赞吧