commonJS简单实现

目标:实现一个可以在浏览器跑通的commonJS。

理解commonJS:主要解决js没有模块化,没有原生的支持密闭作用域或依赖管理的问题;没有包管理系统,不能自动加载和安装依赖;**还有其他原因。

commonJS的工作流程:

1.路径分析

2.文件定位

3.编译执行

本案例直接将一个简单的js文件引用到代码中,并调用exports.方法。

我们来想想我们平时是怎么用commonJS的:???

1.首先通过require('./a.js')方法把a.js文件引用到自己的代码中,然后就可以正常使用a文件中通过module或者exports暴露出来的方法了。

???思考一下,一般引用一个方法都是通过(对象.方法)进行使用的,那require是怎样实现直接使用的呢??

2.那我们就顺着平时的使用进行commnJS的编写。

 

编写思路:

commonJS中最重要的方法就是require方法:整个流程就是:路径分析--文件定位--编译执行。整个案例主要关注的点就是编译执行。

我们将获取到的文件进行编译执行返回一个exports的引用。

1.创建模块,

2.执行文件内容,

3.放进缓存里,

4.返回exports

在这个过程中,首先我们要判断文件是不是已经在缓存里了,如果缓存里有直接从缓存里取出来,

下面是详细代码:

/**
 * 实现一个简单的 commonjs 模块加载器,偏浏览器端的实现
 * 
 * 指导准则:COMMONJS 规范 -- 火狐的一个工程师
 * 
 * 2 个部分:
 * 
 * 1、模块加载器:解析文件地址,有一个寻找的规则,目的肯定就是找到文件
 * 2、模块解析器:执行文件内容的,Node 里面是使用了 v8 执行的
 */

class Module {
  constructor(moduleName, source) {
    // 暴露数据
    this.exports = {};
    // 保存一下模块的信息
    this.moduleName = moduleName;
    // 缓存
    this.$cacheModule = new Map();
    // 源代码
    this.$source = source;
  }

  /**
   * require
   * 
   * useage: require('./a.js')
   * 
   * @param {string} moduleName 模块的名称,其实就是路径信息
   * @param {string} source 文件的源代码,因为省略了加载器部分的实现,所以这里直接传入文件源代码
   * 
   * @return {object} require 返回的结果就是 exports 的引用
   */
  require = (moduleName, source) => {
    // 每一次 require 都执行文件内容的话,开销太大,所以加缓存
    if (this.$cacheModule.has(moduleName)) {
      // 注意,返回的是 exports
      return this.$cacheModule.get(moduleName).exports;
    }

    // 创建模块
    const module = new Module(moduleName, source);

    // 执行文件内容
    const exports = this.compile(module, source);

    // 放进缓存
    this.$cacheModule.set(moduleName, module);

    // 返回 exports
    return exports;
  }

  /**
   * // a.js
   * const b = require('./b.js');
   * 
   * b.action();
   * 
   * exports.action = function() {};
   * 
   * // b.js
   * const a = require('./a.js');
   * 
   * exports.action = function() {};
   */

  /**
   * 拼一个闭包出来,IIFE
   * 
   * @param {string} code 代码字符串
   */
  $wrap = (code) => {
    const wrapper = [
      'return (function (module, exports, require) {',
      '\n});'
    ];

    return wrapper[0] + code + wrapper[1];
  }

  /**
   * 简单实现一个能在浏览器跑的解释器 vm.runInThisContext
   * 核心的点是要创建一个隔离的沙箱环境,来执行我们的代码字符串
   * 
   * 隔离:不能访问闭包的变量 1,不能访问全局的变量 3,只能访问我们传入的变量 2
   * 
   * eval: 可以访问全局/闭包,但是需要解释执行,ES5 之后,如果是间接使用 eval
   *       -> (0, eval)('var a = b + 1'); ❌
   * new Function: 不可以访问闭包,可以访问全局,只编译一次 1 ✅
   * with: with 包裹的对象,会被放到原型链的顶部,而且底层是通过 in 操作符判断的 
上一篇:学习笔记—Node中模块化规范


下一篇:web之js(二)