模块化

Posted by Leonsux on June 14, 2022

为什么要有模块化?

  • 项目庞大时,js文件多,变得不好维护

  • js文件作用域都是顶层,这会造成变量污染

  • js文件依赖问题,稍微不注意顺序引入错,代码全报错

CommonJS

CommonJS 是一个更偏向于服务器端的规范。NodeJS 采用了这个规范。CommonJS 的一个模块就是一个脚本文件。require 命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象缓存下来,后面再加载就不用执行脚本了。

// ./common.js
const fn = () => console.log(1);

const COLORS = ['red', 'blue', 'green'];

module.exports = {
  fn,
  COLORS,
};

// -----
// ./index.js
const common = require('./common.js');

common.fn();
common.COLORS;

在模块化规范 CommonJS 中,通过 requireexports 引入/导出 模块。其中 require 的底层实现为一个 匿名函数自调用(闭包)。模块化要解决的一个核心问题就是 变量名污染。闭包就可以解决这个问题。

const m = (function() {
  const fn = () => console.log(1);

  const COLORS = ['red', 'blue', 'green'];

  return {
    fn,
    COLORS,
  };
})();
  • 使用node的fs模块和path读取对应文件的内容
  • 通过字符串拼接将,fs读取到的字符串拼接起来的,构成该自执行函数
  • 使用vm模块构建一个沙箱环境,执行该自执行函数
  • 返回module.exports,将模块内容返回
const fs = require('fs');
const path = require('path');
const vm = require('vm'); // 虚拟机,提供沙箱环境
const wrapper = [
  '(function(exports, module, require){',
  '})'
]
function Module(absPath) {
  this.id = absPath; // 存储当前路径
  this.exports = {};
}
Module.prototype.load = function() {
  let script = fs.readFileSync(this.id, 'utf8');
  let fnStr = wrapper[0] + script + wrapper[1];
  let fn = vm.runInThisContext(fnStr); // 沙箱执行当前函数
  fn(this.exports, this, req); // 让拼出的函数执行
}
function req(file ) {
  let absPath = path.resolve(__dirname, file); // 1. 获取文件路径
  let module = new Module(absPath); // 2. 创建一个模块
  module.load(); // 3. 加载方法
  return module.exports;
}

let a = req('./a.js')

AMD

AMD 采用异步加载模块,模块加载不影响后面代码执行。依赖该模块的代码写到回调函数中。

模块引入

require(['math'], function(math) {
  // 依赖 math 模块的代码
  math.add(1, 2);
});

console.log('xx'); // 不受上面模块加载影响

模块导出

// math.js
define(function() {
  const add = (x, y) => x + y;

  return {
    add,
  };
});

// or

define(['other'], function(other) { // 当前导出的模块还可以依赖其他模块 other
  const add = (x, y) => x + y;

  return {
    add,
  };
});

主模块统一调度

// main.js
(function () {

  require.config ({
    // baseUrl:'',    
    paths:{   
      dataService:'./dataService',
      math:'./math',
      other: './other',
    }
  })
  require(['dataService'], function (dataService) {
    dataService.doSomething()
  });

})();

ES6 Module

ES6 Module 是静态的,在编译的时候确定模块间的依赖关系。所以 import xx from 'xx' 引入只能声明在文件顶部,不能动态加载。

// ./common.js
export const TYPES = [1, 2, 3];
export const MAX = 9999;

const obj = {
  name: 'leonsux',
  age: 26,
}

export default obj
import obj, { TYPES, MAX as _MAX } from './common.js';

相关文档

https://www.jianshu.com/p/c7ad410e6667

https://www.cnblogs.com/echoyya/p/14577243.html

https://juejin.cn/post/6938581764432461854