欢迎光临
我们一直在努力

JavaScript模块化(CommonJS+ES Module )

模块化标准是规范模块定义、导入、导出的规则,确保不同模块之间可正确交互。前端JavaScript的模块化标准经历了从“民间/社区标准”到“官方标准”的发展,目前主流为ES6 Modules,以下是所有主流标准的详细解析。

模块化解决了这些问题:

  • 函数
  • 解决文件分解(全局污染)和聚合(依赖混乱)的问题

一、民间/社区标准(非官方,早期主流)

1. CommonJS(运行时方案)

简介:Node.js默认采用的模块化方案,基于文件定义模块,每个文件就是一个独立模块,最初用于服务器端,后被前端借用(需通过Webpack等工具打包适配浏览器)。

  • 在 commonjs 中每一个 js 文件都是一个单独的模块,我们可以称之为 module;
  • 该模块中,包含 CommonJS 规范的核心变量: exports、module.exports、require;
  • exports 和 module.exports 可以负责对模块中的内容进行导出;
  • require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;

核心语法(导入/导出):

  • 导出:通过module.exports或exports对象暴露模块成员;

  • 导入:通过require()函数加载模块,同步加载。

代码示例:

// 导出模块(myModule.js)
module.exports = function() {
console.log('Hello from CommonJS');
};

// 导入模块(index.js)
const myModule = require('./myModule');
myModule();

特点:

  • 同步加载:require()调用时,程序会阻塞,等待模块加载完成,适合服务器端(文件读取速度快,阻塞影响小);

  • 运行时解析:依赖关系在代码执行时动态解析,无法在编译阶段优化;

  • 单例模式:每次require()同一个模块,返回的是同一个实例,避免重复加载。

CommonJS 在编译阶段会对模块代码进行首尾包装,形成独立作用域,解决全局污染问题,包装函数如下:

function wrapper (script) {
return '(function (exports, require, module, __filename, __dirname) {' +
script +
'\\n})'
}

  • require 核心机制
  • require是 CommonJS 的核心导入方法,其加载流程、缓存机制、循环引用处理是核心考点。

    (1)文件加载流程

    require 接收标识符作为参数,根据标识符类型按优先级查找模块,优先级:缓存 > 核心模块 > 路径文件模块 > 自定义模块

    • 核心模块:Node.js 底层内置(如 fs、path),已编译为二进制,加载速度最快;
    • 路径文件模块:以./、../、/开头,转换为真实路径后加载;
    • 自定义模块:第三方库(如 crypto-js),从当前目录node_modules开始向上递归查找,优先读取package.json的 main 属性,无则依次查找index.js/.json/.node。
    (2)缓存与防重复加载

    Node.js 存在Module全局对象,用于缓存已加载的模块,核心逻辑:

  • 加载模块时先查找Module._cache,有缓存则直接返回module.exports;
  • 无缓存则创建module对象(含exports: {}、loaded: false),先加入缓存再执行模块代码;
  • 执行完成后标记loaded: true,返回module.exports。效果:同一模块被多次 require 时,仅执行一次,后续直接读取缓存。
  • (3)循环引用处理

    CommonJS 通过先缓存后执行的机制解决循环引用,核心逻辑:

  • 模块 A 加载模块 B 时,先将 A 加入缓存,再执行 A 的代码;
  • A 执行中加载 B,B 加入缓存后执行 B 的代码,B 中再加载 A 时,直接读取 A 的缓存(此时 A 未执行完毕,exports 可能为空 / 部分导出);
  • B 执行完毕后,A 继续执行剩余代码,补全 exports。注意:循环引用时,同步上下文可能获取不到模块未执行的导出内容,可通过异步 / 动态加载解决。
  • (4)动态加载特性

    require 是普通函数,可在任意上下文(函数、条件语句)中动态加载模块,按需引入,示例:

    // a.js
    console.log('我是a文件')
    exports.say = function() {
    // 动态加载b.js
    const getMes = require('./b')
    console.log(getMes())
    }

    2. AMD(Asynchronous Module Definition,异步模块定义)

    简介:专门为浏览器端设计的异步模块化标准,解决CommonJS同步加载在浏览器端的阻塞问题(浏览器加载文件需通过网络,同步加载会导致页面卡顿),常与RequireJS(模块加载器)配合使用。

    核心语法(导入/导出):

    • 导出:通过define()函数定义模块,可指定依赖;

    • 导入:通过require()函数异步加载模块,加载完成后执行回调函数。

    代码示例:

    // 定义模块(myModule.js),依赖dependency模块
    define(['dependency'], function(dependency) {
    return function() {
    console.log('Hello from AMD');
    };
    });

    // 加载模块(index.js)
    require(['myModule'], function(myModule) {
    myModule();
    });

    特点:

    • 异步加载:模块加载不阻塞主线程,适合浏览器环境;

    • 提前声明依赖:define()中需提前指定依赖,加载器可并行加载多个依赖模块;

    • 兼容性:需引入RequireJS等加载器,才能在浏览器中运行。

    3. UMD(Universal Module Definition,通用模块定义)

    简介:一种兼容多模块系统的格式,旨在让同一个模块能在CommonJS、AMD、浏览器全局变量(<script>标签引入)三种环境中无缝运行,解决模块跨环境复用问题。

    核心逻辑:通过判断环境(是否支持AMD、CommonJS),自动适配对应的模块规范。

    代码示例:

    (function (root, factory) {
    if (typeof define === 'function' && define.amd) {
    // 适配AMD环境(如RequireJS)
    define(['b'], factory);
    } else if (typeof exports === 'object') {
    // 适配CommonJS环境(如Node.js)
    module.exports = factory(require('b'));
    } else {
    // 适配浏览器全局变量环境(<script>标签引入)
    root.returnExports = factory(root.b);
    }
    }(this, function (b) {
    // 模块核心逻辑
    return {
    greet: function() {
    console.log('Hello from UMD');
    }
    };
    }));

    二、官方标准(ECMAScript规范,目前主流)

    ES6 Module 是JavaScript 官方原生模块化规范,从 ES6 开始纳入标准,解决了 CommonJS 仅适用于服务端的问题,核心支持静态编译,是现代前端模块化的首选方案。

    1. 核心优势

    • 静态导入导出,支持Tree Shaking(摇树优化,删除未使用代码);
    • 支持import () 动态导入,实现懒加载和代码分割;
    • 原生支持浏览器和服务端(Node.js 14 + 稳定支持);
    • 严格模式运行,避免全局污染。

    2. 核心导入导出语法

    ES Module 通过export导出、import导入,支持命名导出 / 默认导出 / 混合导出

    导出方式示例代码(a.js)导入方式示例代码(main.js)
    命名导出 export const name = 'xxx';export function say() {} 命名导入 import { name, say } from './a.js';
    默认导出 export default { name: 'xxx', author: 'xxx' } 默认导入 import mes from './a.js';(自定义名称)
    混合导出 export const name = 'xxx';export default function say() {} 混合导入 import theSay, { name } from './a.js';
    重命名导出 export { name as bookName } from './a.js'; 重命名导入 import { name as bookName } from './a.js';
    重定向导出 export * from './a.js';(排除 default)
    仅执行模块 仅执行导入 import './a.js';(仅运行,不导出)

    3. 核心特性

    (1)静态语法特性
    • import/export必须放在模块顶层,不能放在块级作用域、条件语句、函数中;
    • 导入名不能是动态字符串(如import ('default' + name) from './a.js');
    • 编译阶段确定依赖关系,支持静态分析、Tree Shaking、类型检查。
    (2)执行特性

    ES Module 采用提前加载 + 深度优先遍历,执行顺序为子 -> 父,与 CommonJS(父 -> 子 -> 父)不同;同时具备缓存机制,同一模块被多次导入时,仅执行一次。

    // main.js
    console.log('main开始')
    import from './a'
    import from './b'
    console.log('main结束')
    // a.js
    import from './b'
    console.log('a加载')
    // b.js
    console.log('b加载')
    // 执行顺序:b加载 → a加载 → main开始 → main结束

    (3)导出绑定特性

    ES Module 的导入变量是只读的、与原模块动态绑定的,核心规则:

  • 导入变量默认为const,直接赋值会报错(如num = 2);
  • 变量是引用传递,原模块修改变量,导入方会实时同步;
  • 可通过原模块的方法修改变量,导入方获取最新值。
  • // a.js
    export let num = 1
    export const add = () => { num++ }
    // main.js
    import { num, add } from './a.js'
    console.log(num) // 1
    add()
    console.log(num) // 2
    num = 3 // 报错:num is read-only

    4. import () 动态导入

    为解决 ES Module 静态语法的局限性,提供import () 动态导入特性,是现代前端懒加载的核心实现。

    核心特点
    • 函数形式,可在任意上下文中使用(条件、函数);
    • 返回Promise 对象,then 回调中获取模块导出内容;
    • 支持代码分割,按需加载,避免首次加载过大。

    // 动态加载b.js
    setTimeout(() => {
    import('./b.js').then(res => {
    console.log(res.name) // 命名导出
    console.log(res.default) // 默认导出
    })
    }, 0)

    实际应用
    • Vue 路由懒加载:component: () => import('./Home.vue');
    • React 懒加载:React.lazy(() => import('./LazyComponent'));
    • 按需加载业务模块:条件判断加载不同的功能模块。

    5. Tree Shaking 实现

    Tree Shaking 是 ES Module 的核心优化,依赖静态语法特性,在编译阶段分析模块导出与导入的引用关系,删除未被使用的代码,减少打包体积。

    // a.js:导出3个成员,仅add被使用
    export let num = 1
    export const add = () => { num++ }
    export const del = () => { num– }
    // main.js:仅导入add
    import { add } from './a.js'
    add()
    // 打包结果:仅保留num和add,del被删除

    三、CommonJS 与 ES Module 核心对比

    对比维度CommonJSES Module
    标准类型 社区规范,非官方 ES6 官方原生规范
    运行环境 主要适用于 Node.js(服务端),前端需打包兼容 浏览器 + Node.js(14+)原生支持
    加载方式 同步加载,运行时解析 静态编译(顶层)+ 异步加载(import ()),编译时解析
    执行顺序 深度优先,父 -> 子 -> 父 深度优先,子 -> 父
    导出方式 单个值导出,最终导出 module.exports 支持多成员命名导出、默认导出
    变量绑定 值拷贝,基本类型导出值,引用类型导出地址 动态绑定,引用传递,原模块修改实时同步
    导入变量特性 可修改导入的引用类型属性 导入变量只读,不可直接赋值
    核心优化 无原生优化,依赖打包工具 支持 Tree Shaking、代码分割(import ())
    循环引用处理 先缓存后执行,同步上下文可能获取不到完整导出 静态解析,提前确定依赖,绑定关系实时同步
    缓存机制 基于 Module._cache 缓存 module 对象 模块缓存,同一模块仅执行一次
    赞(0)
    未经允许不得转载:171主机测评 » JavaScript模块化(CommonJS+ES Module )
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址