js模块化——commonJS

一、简介

CommonJS, AMD, CMD都是JS模块化的规范。
CommonJS服务器端js模块化的规范,NodeJS是这种规范的实现。
AMD(异步模块定义)和CMD(通用模块定义)都是浏览器端js模块化的规范。RequireJS 遵循的是 AMDSeaJS 遵循的是 CMD

二、node中commonJS的简单实现

CommonJS的核心思想就是通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口。

1.每个模块都是Module(由node提供)的一个实例

function Module(id, parent) {
  this.id = id; //模块的识别符,通常是带有绝对路径的模块文件名
  this.exports = {}; // 表示模块对外输出的值,默认值为空对象
  this.parent = parent; //返回一个对象,表示调用该模块的模块
  this.filename = null; //模块的文件名,带有绝对路径
  this.loaded = false; // 返回一个布尔值,表示模块是否加载
  this.children = []; //返回一个数组,表示该模块要用到的其他模块
}
module.exports = Module;
let module = new Module(filename, parent)

2.require的本质就是执行了下面的闭包

(function(exports, require, module, __filename, __dirname) {
  //导出方法
  module.exports = exports = function sum(a, b) {
    return a + b;
  };
  //不能改变exports指向,因为返回的是module.exports,所以是个{}
  return module.exports;
});

3.require的流程

js模块化——commonJS_第1张图片

4.require的简单实现

  • 准备工作(需要引入node中内置的几个包)
//操作文件的模块
let fs = require('fs')

// 处理路径的模块
let path = require('path')

// 虚拟机模块,沙箱运行,防止变量污染
let vm = require('vm')
  • 在Module(构造函数)上挂个_resolveFilename函数和_extensions对象,根据相对路径返回绝对路径
//该对象为不同的文件类型声明对应的处理函数(这里只是大概描述一下,之后会完善该对象)
Module._extensions = { js: () => {}, json: () => {} };
Module._resolveFilename = r_path => {
  //如果相对路径有后缀名js或json,则返回绝对路径
  if (/\.js$|\.json$/.test(r_path)) {
    return path.resolve(__dirname, r_path);
  } else {
    let exts = Object.keys(Module._extensions); //取得所有可能的后缀名
    let real_name;
    for (let i = 0; i < exts.length; i++) {
      try {
        //查看该文件是否存在
        fs.accessSync(r_path + '.' + exts[i]);
        real_name = r_path + exts[i];
      } catch (error) {}
    }
    //如果找到了该文件
    if (real_name) {
      return path.resolve(__dirname, real_name);
    } else {
      throw new Error('module not exist');
    }
  }
};
  • 同样在Module上挂个存放缓存的对象_cacheModules(当用户重复加载一个已经加载过的模块,我们只有第一次是加载,然后放入缓存中,后面在加载时,直接返回缓存中即可)
//缓存对象
Module._cacheModules = {};
//命中则返回缓存中的模块,没命中则新建一个模块对象且加入缓存
Module.catchCache = real_path => {
  if (Module._cacheModules[real_path]) {
    return Module._cacheModules[real_path];
  } else {
    Module._cacheModules[r_path] = new Module(r_path);
    return Module._cacheModules[r_path];
  }
};
  • 完善前面提到的_extensions
//前面所提到的闭包的字符串表示
Module.wrapper = ['function(exports,require,module){', '}'];
//读取文件内容作为闭包函数体
Module.wrap = content => {
  return Module.wrapper[0] + content + Module.wrapper[1];
};
//完善_extensions
Module._extensions = {
  js: module => {
    //同步读取文件
    let content = fs.readFileSync(module.id, 'utf8');
    //拼接成完整的函数
    let func_str = Module.wrapper(content);
    //沙箱运行该函数
    vm.runInThisContext(func_str).call(
      module.exports,
      module.exports,
      require,
      module
    );
  },
  json: module => {
    //将字符串转为JSON,并赋值给module.exports
    let content = fs.readFileSync(module.id, 'utf8');
    module.exports = JSON.parse(content);
  }
};
  • 加载模块

function tryModuleLoad(module) {
  //获取后缀
  let ext = path.extname(module.id);
  //根据文件类型用相应的函数处理
  Module._extensions[ext](module);
}
Module._load = r_path => {
  //获取绝对路径
  let real_path = Module._resolveFilename(r_path);
  //命中缓存或新建模块
  let module = Module.catchCache(real_path);
  //将文件中导出的赋值给module.exports
  tryModuleLoad(module);
  return module.exports;
};
//最终的require API
function require(r_path) {
  return Module._load(r_path);
}
  • 最终我们就可以使用require来引入我们所需的模块
let a = require('./a.js');
exports.sum = (a, b) => {
  console.log(a + b);
};

你可能感兴趣的:(js模块化)