Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术随笔

    • git教程
    • 前端模块化:CommonJS,AMD,CMD,ES6
      • 0.前言
      • 1.CommonJS (Node.js)
      • 2.AMD (require.js)
      • 3.CMD (sea.js)
      • 4.ES6 (浏览器和服务器通用模块解决方案)
      • ✡️ 5.重点:ES6 和 CommonJS 模块的差异
      • 6.Node中使用ES6模块
      • 7.同时兼容两种模块的方式
      • 8.动态 import ()
      • 本篇blog未理解知识点
      • 参考资料:
    • 如何动态加载本地图片
    • GitHub自动部署脚本
    • React生命周期详解
    • 将父组件的props作为组件的初始state的几种方案
    • webpack基础入门
    • StackOver记录:如何在function中使用redux
    • React中使用onClick时的三种性能优化
    • 思考React状态管理
    • forEach/for..in/of
    • styled-components使用记录
    • 深入理解forEach与forof
    • Node技术架构
    • Redux、Mobx状态管理杂谈
    • JS中的类型检测
    • 如何判断图片是否在可视区域
    • 如何使用图片的懒加载操作
    • JavaScript模块化:从CommonJS到AMD
    • git查看:修改用户名、密码
    • 保持数据Immutable的几种写法
    • 如何实现一个Promise.all
    • resolve(Promise对象)会多出两个微任务
    • todo_基于Mocha的测试驱动开发
    • 浏览器页面渲染机制
    • todo-响应式编程之数据劫持
    • todo-immer库的介绍
    • React-Hooks 基础原理解析
    • JS基本功
    • 函数闭包与this指针
    • 收集 DUMI 配置过程中遇到的问题
    • typescript 类型工具
    • 统一接口的导入导出
    • 网络安全:如何预防 XSS 攻击
    • 浏览器页面渲染机制【2023】
    • 跨域资源请求
    • 浏览器事件循环机制
    • 如何理解浏览器的 user agent 用户代理的含义?
  • 转载文章

  • Mac相关

  • 前端学习周报

  • 包管理工具

  • 技术随笔
  • 技术随笔
wangjiasheng
2021-10-07
目录

前端模块化:CommonJS,AMD,CMD,ES6

# 0.前言

本篇博客介绍的是前端模块化语法规范,通过模块化开发的方式可以极大的提高代码的复用率,以及结构化项目的管理。在模块化语法规范中,一个文件就是一个模块,每个模块拥有各自的作用域(模块作用域),只向外暴露特定的变量和函数。

目前流行的 js 模块化规范有 CommonJS、AMD、CMD 以及 ES6 的模块系统。

# 1.CommonJS (Node.js)

Node.js 是 commonJS 规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports ),用 require 加载模块。

// 定义模块math.js
var basicNum = 0;
function add(a, b) {
  return a + b;
}

// 导出模块(export带s)
module.exports = { //在这里写上需要向外暴露的函数、变量
  add: add,
  basicNum: basicNum
}
// 或者 module.export.add = add 

// 导入模块时可省略.js
var math = require('./math');
math.add(2, 5);

// 当导入的是Node的核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

commonJS 的特点:同步、服务器端

  1. 在Node服务器端,模块文件都是存在本地磁盘,因此 commonJS 使用的是同步的方式加载模块。
  2. 在浏览器端,限于网络原因,更合理的方式使用异步加载。因此commonJS更使用于服务端的加载。

# 2.AMD (require.js)

AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。

此规范暂时用不到,省略,用时可查:https://juejin.cn/post/6844903576309858318 (opens new window)

# 3.CMD (sea.js)

CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。

此规范暂时用不到,省略,用时可查:https://juejin.cn/post/6844903576309858318 (opens new window)

# 4.ES6 (浏览器和服务器通用模块解决方案)

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };
// 或者直接 export let num = 1
// 或者直接 export function setNUm(){}

/** 引用模块(import作为关键字) **/ 
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

除上例所示,使用import 命令时,直接导出的已知的变量名或函数名。使用export default 命令,可以 module 的方式导出,对应的import语句不需要使用大括号。

/** 导出模块(default) **/ 
export default { basicNum, add }; // 就多加了 default

/** 导入模块(default) **/ 
import math from './math';
console.log(math.add(1,2)); // 读取函数
console.log(math.basicNum);  // 读取变量名

function test(ele) {
    ele.textContent = math.add(99 + math.basicNum);
}
1
2
3
4
5
6
7
8
9
10
11

# ✡️ 5.重点:ES6 和 CommonJS 模块的差异

讨论 Node.js 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。

它们有三个重大差异:

  1. CommonJS 模块输出的是一个值的拷贝;ES6 模块输出的是值的引用。

    // es6.mjs (注意这里是mjs,因为当在node中使用es6必须要告诉node)
    let num = 1;
    function setNum(newNum) {
        num = newNum
    }
    export { num, setNum } // es6导出语法
    module.export.num = num  // commonJs导出语法
    module.export.setNum = setNum // commonJs导出语法
    
    // main.mjs (同理,在导入时也需要告诉node当前的js是ES6 module)
    import {num,setNum} from './es6.mjs'
    console.log(num)  // 打印:1 
    setNum(2)
    console.log(num) // 打印:2 (说明ES6是值索引,因为它修改了内部的num变量)
    
    // commonJS 会缓存 _module.num 
    let _module =  require('./demo01-es6-and-CommonJS-cmj.js')
    console.log(_module.num) // 打印:1 
    _module.setNum(2)
    console.log(_module.num) // 打印:1 (_module.num 是一个原始类型的值,会被缓存)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    结论:

    • ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块(就好像 module.mjs 的文件被直接写在了main.mjs 文件中,专业的说法:JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。)

    • CommonJS 则是把原先的module又给拷贝了一份(且是一种浅拷贝)。

    CommonJS若也想达到ES6的值引用效果,可以将num变为一个函数(取值器函数getter)

    let num = 1;
    module.exports = {
      get num(){
        return num 
      }
      setNum: setNum
    } 
    
    1
    2
    3
    4
    5
    6
    7
  2. CommonJS 模块是运行时加载;ES6 模块是编译时输出接口。

    这一特性使得ES6模块支持静态代码分析,如果代码写错,可以直接提示问题;而CommonJS 模块的输出接口是module.exports,是一个对象,无法被静态分析,所以只能整体加载。

  3. CommonJS 模块的require()是同步加载模块;ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

    这一特性决定了:在cjs文件中无法使用require()函数加载mjs模块,只能使用import()函数。

# 6.Node中使用ES6模块

JavaScript 现在有两种模块。一种是 ES6 模块,简称 ESM;另一种是 CommonJS 模块,简称 CJS。

CommonJS 模块是 Node.js 专用的,与 ES6 模块不兼容。语法上面,两者最明显的差异是,CommonJS 模块使用require()和module.exports,ES6 模块使用import和export。

有两种方式:

  1. 更改后缀名:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载
  2. 若.js文件,可在项目的package.json文件中,指定type字段为module。

# 7.同时兼容两种模块的方式

以下是.cjs 即为CommonJS模块中使用ES6模块,以import关键字的方式会报错。

// commonJS 会缓存 _module.num 
let _module =  require('./demo01-es6-and-CommonJS-cmj.cjs') //cjs加载cjs合理
import './demo01-es6-and-CommonJS-es6.mjs' // 错误
import('./demo01-es6-and-CommonJS-es6.mjs').then((module)=>{
    console.log('es6',module.num) // 1
    module.setNum(3)
    console.log('es6',module.num) // 3
})
console.log(_module.num) // 1 
_module.setNum(2)
console.log(_module.num) // 2 
1
2
3
4
5
6
7
8
9
10
11

第二种方式是修改package.json的exports字段:

"exports":{
  "require": "./index.js",
  "import": "./esm/wrapper.js"
}
1
2
3
4

# 8.动态 import () (opens new window)

ES6 模块在编译时就会静态分析,优先于模块内的其他内容执行,所以导致了我们无法写出像下面这样的代码:

if(some condition) {
  import a from './a';
}else {
  import b from './b';
}
1
2
3
4
5

因为编译时静态分析,导致了我们无法在条件语句或者拼接字符串模块,因为这些都是需要在运行时才能确定的结果在 ES6 模块是不被允许的,所以 动态引入import()应运而生。

动态 import的基本使用上面已经用过了,这里主要是使用Promise.all进行并行异步加载。

Promise.all([
  import('./a.js'),
  import('./b.js'),
  import('./c.js'),
]).then(([a, {default: b}, {c}]) => {
    console.log('a.js is loaded dynamically');
    console.log('b.js is loaded dynamically');
    console.log('c.js is loaded dynamically');
});
1
2
3
4
5
6
7
8
9

还有 Promise.race 方法,它检查哪个 Promise 被首先 resolved 或 reject。我们可以使用import()来检查哪个CDN速度更快:

const CDNs = [
  {
    name: 'jQuery.com',
    url: 'https://code.jquery.com/jquery-3.1.1.min.js'
  },
  {
    name: 'googleapis.com',
    url: 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'
  }
];

console.log(`------`);
console.log(`jQuery is: ${window.jQuery}`);

Promise.race([
  import(CDNs[0].url).then(()=>console.log(CDNs[0].name, 'loaded')),
  import(CDNs[1].url).then(()=>console.log(CDNs[1].name, 'loaded'))
]).then(()=> {
  console.log(`jQuery version: ${window.jQuery.fn.jquery}`);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

当然,如果你觉得这样写还不够优雅,也可以结合 async/await 语法糖来使用。

async function main() {
  const myModule = await import('./myModule.js');
  const {export1, export2} = await import('./myModule.js');
  const [module1, module2, module3] =
    await Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ]);
}
1
2
3
4
5
6
7
8
9
10

动态 import() 为我们提供了以异步方式使用 ES 模块的额外功能。 根据我们的需求动态或有条件地加载它们,这使我们能够更快,更好地创建更多优势应用程序。

# 本篇blog未理解知识点

  1. CommonJS 和 ES6 模块是如何解决循环依赖的。
  2. 看了方应杭的webpack,才发现import的支持分为两种,一是以type="module"的方式,同时运行多个文件的方式。但是这种方式在浏览器中是不可靠的,因为需要下载的文件太多,一般是通过静态分析的方式,将多个文件合并成一个文件,并且转译为require

# 参考资料:

  1. 掘金总结文章:https://juejin.cn/post/6844903576309858318 (opens new window)
  2. 阮一峰写的电子书:https://es6.ruanyifeng.com/#docs/module-loader (opens new window)
编辑 (opens new window)
#模块化
上次更新: 2022/04/06, 15:04:00
git教程
如何动态加载本地图片

← git教程 如何动态加载本地图片→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式