CommonJS 和 ES6module 的区别
动态与静态
CommonJS 与 ES6 Module 最本质的区别在于前者对模块依赖的解决是“动态的”,而后者是“静态的”。在这里“动态”的含义是,模块依赖关系的建立发生在代码运行阶段:而“静态”则表示模块依赖关系的建立发生在代码编译阶段。
看一个 CommonJS 的例子:
// calculator.js
module.exports = { name: 'calculator' };
// index.js
const name = require('./calculator.js').name;
模块 A 在加载模块 B 时会执行 B 中的代码,并将其 module.exports 对象作为 require 函数的返回值返回。require的模块路径可以动态指定,支持传入一个表达式,甚至可以通过 if 语句判断是否加载某个模块。因此,在 CommonJS 模块被执行前,我们并没有办法确定明确的依赖关系,模块的导入、导出发生在代码的运行阶段。
针对同样的例子,我们再对比看下 ES6 Module 的写法:
// calculator.js
export const name ='calculator';
// index.js
import name from './calculator.js';
ES6 Module 的导入、导出语句都是声明式的,它不支持将表达式作为导入路径,并且导入、导出语句必须位于模块的顶层作用域(比如不能放在语句中)。因此我们说,ES6 Module是一种静态的模块结构,在 ES6 代码的编译阶段就可以分析出模块的依赖关系。它相比 CommonJS 来说具备以下几点优势:
- 死代码检测和排除。我们可以用静态分析工具检测出哪些模块没有被调用过。比如,在引入工具类库时,工程中往往只用到了其中一部分组件或接口,但有可能会将其代码完整地加载进来。未被调用到的模块代码永远不会被执行,也就成了死代码。通过静态分析可以在打包时去掉这些未曾使用过的模块,以减小打包资源体积。
- 模块变量类型检查。JavaScript 属于动态类型语言,不会在代码执行前检查类型错误(比如对一个字符串类型的值进行函数调用)。ES6 Module 的静态模块结构有助于确保模块之间传递的值或接口类型是正确的。
- 编译器优化。在 CommonJS 等动态模块系统中,无论采用哪种方式,本质上导入的都是一个对象,而 ES6 Module 支持直接导入变量,减少了引用层级,程序效率更高。
值复制与动态映射
在导入一个模块时,对于 CommonJS 来说获取的是一份导出值的副本;而在 ES6 Module 中则是值的动态映射,并且这个映射是只读的。看一个例子,了解一下什么是 CommonJS 中的值复制:
// calculator.js
var count = 0;
module.exports = {count: count,add: function(a,b){count += 1;return a + b;}
}// index.js
var count = require('./calculator.js').count;
var add = require('./calculator.js').add;
console.log(count); // 0(这里的count是calculator.js中count值的副本)
add(2, 3);
console.log(count); // 0(calculator.js中变量值的改变不会对这里的副本造成影响)
count += 1;
console.log(count); // 1(副本的值可以更改)
index.js 中的 count 是 calculator.js 中 count 的一份副本,因此在调用 add 函数时,虽然更改了原本 calculator.js 中 count 的值,但是并不会对 index.js 中导入时创建的副本造成影响。
另一方面,在 CommonJS 中允许对导入的值进行更改。我们可以在 index.js 中更改 count 和 add ,将其赋予新值。同样,由于是值的副本,这些操作不会影响 calculator.js 本身。
下面使用 ES6 Module 对上面的例子进行改写:
// calculator.js
let count = 0;
const add = function(a, b){count += 1;return a + b;
}
export { count, add }
// index.js
import { count, add } from'./calculator.js';
console.log(count); // 0(对calculator.js中count值的映射)
add(2, 3);
console.log(count); // 1(实时反映calculator.js中count值的变化)
// count += 1; // 不可更改,会抛出SyntaxError:"count”is read-only
上面的例子展示了 ES6 Module 中导入的变量其实是对原有值的动态映射 index.js 中的 count 是对 calculator.js 中 count 值的实时反映,当我们通过调用 add 函数更改了 calculator.js 中的 count 值时,index.js 中 count 的值也随之变化。并且 ES6 Module 规定不能对导入的变量进行修改,当我们尝试去修改时它会抛出该变量只读的错误。
循环依赖
循环依赖是指模块 A 依赖于模块 B ,同时模块 B 依赖于模块 A ,或者是 A 依赖于 B ,B 依赖于 C ,C 依赖于 D ,最后绕了一大圈,D 又依赖于 A 。当中间模块太多时我们就很难发现 A 和 B 之间存在隐式的循环依赖了。
因此,如何处理循坏依赖是开发者必须要面对的问题。首先看一下在 CommonJS 中循环依赖的例子:
// foo.js
const bar = require('./bar.js');
console.log('value of bar:', bar);
module.exports ='This is foo.js';
// bar.js
const foo = require('./foo.js');
console.log('value of foo:', foo);
module.exports ='This is bar.js';
// index.js
require('./foo.js');
而当我们运行上面的代码时,实际输出却是:
value of foo:{}
value of bar:This is bar.js
为什么 foo 的值会是一个空对象呢?让我们从头梳理一下代码的实际执行顺序:
- index.js 导入了 foo.js ,此时开始执行 foo.js 中的代码。
- foo.js 的第 1 句导入了 bar.js ,这时 foo.js 不会继续向下执行,而是会进入 bar.js 内部。
- 在 bar.js 中又对 foo.js 进行了导入,这里产生了循环依赖。需要注意的是,执行权并不会再交回 foo.js,而是直接取其导出值,也就是
module.exports。但由于 foo.js 未执行完毕,导出值在这时为默认的空对象,因此当 bar.js 执行到打印语句时,我们看到控制台中的 value of foo 就是一个空对象。 - bar.js 执行完毕,将执行权交回 foo.js 。
- foo.js 从
require语句继续向下执行,在控制台打印出 value of bar (这个值是正确的),整个流程结束。
接下来我们使用 ES6 Module 的方式重写上面的例子:
// foo.js
import bar from './bar.js'
console.log('value of bar:', bar);
export default 'This is foo.js';
// bar.js
import foo from './foo.js';
console.log('value of foo:', foo);
export default This is bar.js';
// index.js
import foo from './foo.js';
执行结果如下:
value of foo:undefined
foo.js:3 value of bar:This is bar.js
很遗憾,在 bar.js 中同样无法得到 foo.js 正确的导出值,只不过和 CommonJS 默认导出一个空对象不同,这里获取到的是 undefined 。
上面我们谈到,在导入一个模块时,CommonJS 获取到的是值的副本,ES6 Module 则是动态映射,那么我们能否利用 ES6 Module 的特性使其支持循环依赖呢?请看下面这个例子:
// index.js
import foo from './foo.js';
foo ('index.js');
// foo.js
import bar from './bar.js'
function foo(invoker){console.log(invoker + 'invokes foo.js');bar ('foo.js');
}
export default foo;
// bar.js
import foo from './foo.js'
let invoked = false;
function bar (invoker){if(!invoked){invoked = true;console.log(invoker + 'invokes bar.js');foo ('bar.js');}
}
export default bar;
上面代码的执行结果如下:
index.js invokes foo.js
foo.js invokes bar.js
bar.js invokes foo.js
可以看到,foo.js 和 bar.js 这一对循环依赖的模块均获取到了正确的导出值。下面让我们分析一下代码的执行过程。
- index.js 作为入口导入了 foo.js ,此时开始执行 foo.js 中的代码。
- 从 foo.js 导入 bar.js ,,执行权交给 bar.js 。
- 在 bar.js 中一直执行到结束,完成 bar 函数的定义。注意,此时由于 foo.js 还没执行完,
foo的值现在仍然是undefined。 - 执行权回到 foo.js 继续执行直到结束,完成
foo函数的定义。由于 ES6 Module 动态映射的特性,此时在 bar.js 中foo的值已经从undefined成为我们定义的函数,这是与 CommonJS 在解决循环依赖时的本质区别,CommonJS 中导入的是值的副本,不会随着模块中原有值的变化而变化。 - 执行权回到 index.js 并调用
foo函数,此时会依次执行 foo一bar一foo ,并在控制台输出正确的值。
由上面的例子可以看出,ES6 Module 的特性使其可以更好地支持循环依赖,只是需要由开发者来保证当导入的值被使用时已经设置好正确的导出值。
相关文章:
CommonJS 和 ES6module 的区别
动态与静态 CommonJS 与 ES6 Module 最本质的区别在于前者对模块依赖的解决是“动态的”,而后者是“静态的”。在这里“动态”的含义是,模块依赖关系的建立发生在代码运行阶段:而“静态”则表示模块依赖关系的建立发生在代码编译阶段。 看一…...
IM 即时通讯系统-51-MPush开源实时消息推送系统
IM 开源系列 IM 即时通讯系统-41-开源 野火IM 专注于即时通讯实时音视频技术,提供优质可控的IMRTC能力 IM 即时通讯系统-42-基于netty实现的IM服务端,提供客户端jar包,可集成自己的登录系统 IM 即时通讯系统-43-简单的仿QQ聊天安卓APP IM 即时通讯系统-44-仿QQ即…...
前端 | JavaScript中的reduce方法
1. 什么是reduce reduce 方法是 JavaScript 中数组的重要方法之一,用于对数组中的元素进行累积计算。它接收一个回调函数作为参数,并返回一个最终计算结果。reduce 在许多场景下都非常有用,比如求和、数组扁平化、对象计数、数据转换等。 2…...
【Linux】从硬件到软件了解进程
个人主页~ 从硬件到软件了解进程 一、冯诺依曼体系结构二、操作系统三、操作系统进程管理1、概念2、PCB和task_struct3、查看进程4、通过系统调用fork创建进程(1)简述(2)系统调用生成子进程的过程〇提出问题①fork函数②父子进程关…...
2024-我的学习成长之路
因为热爱,无畏山海...
机试题——到邻国目标城市的最短距离
题目描述 A国与B国是相邻的两个国家,每个国家都有很多城市。国家内部有很多连接城市的公路,国家之间也有很多跨国公路,连接两个国家的边界城市。两个国家一共有N个城市,编号从1到N,一共有M条公路,包括国内…...
连续预测、
一、连续预测 调用模型遍历需要预测文件夹中的图片: image_ids open(‘VOCdevkit/VOC2007/ImageSets/Main/test.txt’).read().strip().split() for image_id in tqdm(image_ids): # 遍历测试图像 image_path “./VOCdevkit/VOC2007/JPEGImages/” image_id …...
Kamailio 不通过 dmq 实现注册复制功能
春节期间找到一篇文章,需要 fg 才能看到: https://medium.com/tumalevich/kamailio-registration-replication-without-dmq-65e225f9a8a7 kamailio1 192.168.56.115 kamailio2 192.168.56.116 kamailio3 192.168.56.117 route[HANDLE_REPLICATION] {i…...
002 mapper代理开发方式-xml方式
文章目录 代理xml方式UserMapper.javaUser.javadb.propertiesSqlMapConfig.xmlUserMapper.xmlUserMapperTest.javapom.xml 代理 此处使用的是JDK的动态代理方式,延迟加载使用的cglib动态代理方式 代理分为静态代理和动态代理。此处先不说静态代理,因为…...
大模型系列21-AI聊天机器人
聊天机器人 背景机器学习基础监督学习(Supervised Learning)概念应用场景主要问题 无监督学习(Unsupervised Learning)概念常见方法应用场景 强化学习(Reinforcement Learning)概念关键要素应用场景 模型优…...
Apache Iceberg数据湖技术在海量实时数据处理、实时特征工程和模型训练的应用技术方案和具体实施步骤及代码
Apache Iceberg在处理海量实时数据、支持实时特征工程和模型训练方面的强大能力。Iceberg支持实时特征工程和模型训练,特别适用于需要处理海量实时数据的机器学习工作流。 Iceberg作为数据湖,以支持其机器学习平台中的特征存储。Iceberg的分层结构、快照…...
25.2.3 【洛谷】作为栈的复习不错(学习记录)
今天学习的东西不算多,放了一个星期假,感觉不少东西都没那么清楚,得复习一下才行。今天搞个栈题写,把栈复习一下,明天进入正轨,边复习边学习新东西,应该会有二叉树的学习等等... 【洛谷】P1449 …...
Windows 中的 WSL:开启你的 Linux 之旅
今天在安装windows上安装Docker Desktop的时候,遇到了WSL。下面咱们就学习下。 欢迎来到涛涛聊AI 一、什么是 WSL? WSL,全称为 Windows Subsystem for Linux,是微软为 Windows 系统开发的一个兼容层,它允许用户在 Win…...
二维前缀和:高效求解矩阵区域和问题
在处理二维矩阵时,频繁计算某一子矩阵的和是一个常见的操作。传统的做法是直接遍历该子矩阵,时间复杂度较高。当矩阵非常大且有大量的查询时,直接计算将变得低效。为了提高效率,我们可以通过 二维前缀和 技巧在常数时间内解决这个…...
音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现
一、引言 FFmpeg源码中通过ff_sdp_parse函数解析SDP。该函数定义在libavformat/rtsp.c中: int ff_sdp_parse(AVFormatContext *s, const char *content) {const char *p;int letter, i;char buf[SDP_MAX_SIZE], *q;SDPParseState sdp_parse_state { { 0 } }, *s1…...
Android开发工作经历整理
一.无人机应用软件开发 集成大疆官网的DJIMobileSDK到AS中编写软件,操控无人机执行多个航点任务。集成OpenCV库进行图像识别,通过获取参数,根据算法执行sdk,使无人机降落到机库,并执行后续的换电操作。待无人机就绪后…...
C++中常用的十大排序方法之4——希尔排序
成长路上不孤单😊😊😊😊😊😊 【😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于C中常用的排序方法之4——希尔排序的相…...
解决注入线程池的栈溢出问题
文章目录 1.问题产生2.问题解决 1.问题产生 在使用sleuth的时候,需要注入线程池,他才会自动包装,实现traceId的传递,但是突然启动时出现了栈溢出的问题 2.问题解决 根据报错,发现是Gson序列化相关的问题,…...
自动驾驶---两轮自行车的自主导航
1 背景 无人驾驶汽车最早出现在DARPA的比赛中,从那个时刻开始,逐渐引起全球学者的注意,于是从上个世纪开始各大高校院所开始了无人汽车的研发。直到这两年,无人驾驶汽车才开始走进寻常百姓家,虽然目前市面上的乘用车还…...
哈夫曼树并查集
(1)哈夫曼树 特殊概念: 1.结点的权:表示结点树的重要性 2.带权路径长度:从树的根到该节点的路径长度(经过的边数)与该节点上权值的乘积 2.树的带权路径长度:该树的所有叶子节点的…...
PyTorch数据建模
回归分析 import torch import numpy as np import pandas as pd from torch.utils.data import DataLoader,TensorDataset import time strat = time.perf_counter()...
在 Ubuntu 上安装 Node.js 23.x
在 Ubuntu 上安装 Node.js 23.x 前提条件安装步骤1. 下载设置脚本2. 运行设置脚本3. 安装 Node.js4. 验证安装 参考链接总结 在现代 web 开发中,Node.js 是一个不可或缺的工具。它提供了一个强大的 JavaScript 运行时环境,使得开发人员可以在服务器端使用…...
SQL范式与反范式_优化数据库性能
1. 引言 什么是SQL范式 SQL范式是指数据库设计中的一系列规则和标准,旨在减少数据冗余、提高数据完整性和一致性。常见的范式包括第一范式(1NF)、第二范式(2NF)、第三范式(3NF)和BCNF(Boyce-Codd范式)。 什么是SQL反范式 SQL反范式是指在满足范式要求的基础上,有…...
hunyuan 混元学习
使用了5个subset,也是用了text-image和text-video进行训练的 也是进行了复杂的视频选择。同movie gen. 也进行了模型切断,用拉普拉斯算子找到最清晰的一帧作为训练的起始 训练了不同的模型去选择数据,比如用Dover去选择美观度比较好的数据,…...
四、GPIO中断实现按键功能
4.1 GPIO简介 输入输出(I/O)是一个非常重要的概念。I/O泛指所有类型的输入输出端口,包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO(General-Purpose Input/Output)则是一个常见的术语,…...
.Net / C# 繁体中文 与 简体中文 互相转换, 支持地方特色词汇
版本号 Nuget 搜索 “OpenCCNET”, 注意别找错, 好多库的名字都差不多 支持 “繁,简” 的互相转换, 支持多个地区常用词汇的转换, 还支持 日文的新旧转换. OpenCC 在 .Net 中的实现 https://github.com/CosineG/OpenCC.NET <PackageReference Include"OpenCCNET"…...
一元函数微积分的几何应用:二维平面光滑曲线的曲率公式
文章目录 前言曲率和曲率半径的定义曲率计算公式参数方程形式直角坐标显式方程形式极坐标形式向量形式 前言 本文将介绍二维平面光滑曲线的曲率定义以及不同形式的曲率及曲率半径公式的推导。 曲率和曲率半径的定义 (关于二维平面光滑曲线的定义以及弧长公式请参…...
数据结构与算法之异步: LeetCode 1114. 按序打印 (Ts版)
按序打印 https://leetcode.cn/problems/print-in-order/description/ 描述 给你一个类: public class Foo {public void first() { print("first"); }public void second() { print("second"); }public void third() { print("third&qu…...
python:求解爱因斯坦场方程
在物理学中,爱因斯坦的广义相对论(General Relativity)是描述引力如何作用于时空的理论。广义相对论由爱因斯坦在1915年提出,并被阿尔伯特爱因斯坦、纳森罗森和纳尔逊曼德尔斯塔姆共同发展。广义相对论的核心方程是爱因斯坦场方程…...
PostgreSQL 数据备份与恢复:掌握 pg_dump 和 pg_restore 的最佳实践
title: PostgreSQL 数据备份与恢复:掌握 pg_dump 和 pg_restore 的最佳实践 date: 2025/1/28 updated: 2025/1/28 author: cmdragon excerpt: 在数据库管理中,备份与恢复是确保数据安全和业务连续性的关键措施。PostgreSQL 提供了一系列工具,以便于数据库管理员对数据进行…...
