Commonjs和Es6语法规范的理解

ES6 module和CommonJS到底有什么区别?

“ES6 module是编译时加载,输出的是接口,CommonJS运行时加载,加载的是一个对象”

这里的“编译时”是什么意思?和运行时有什么区别?“接口”又是什么意思?

“ES6 模块输出的是值的引用,CommonJS 模块输出的是一个值的拷贝”

那么“值的引用”和“值的拷贝”对于开发者又有什么区别呢?

下面通过一些示例详细说明ES6 module和CommonJS的5点区别

1. 编译时导出接口 VS 运行时导出对象

CommonJS 模块是运行时加载,因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。

ES6 模块是它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

这里的“编译时”,指的是js代码在运行之前的编译过程,我们熟悉的变量提升就发生在编译阶段,但是由于编译过程是引擎的行为,开发者没法在编译阶段做任何操作,所以不容易直观地理解“编译时导出接口和运行时导出对象”这个区别。

不过我们在循环引用这个场景就可以轻松地理解两者的差异。

来看下面CommonJS的代码

// index.js
const {log} = require('./lib.js');
module.exports = {name: 'index'
};
log();// lib.js
const {name} = require('./index.js');
module.exports = {log: function () {console.log(name);}
};

执行index.js:node index.js结果会打印"undefined"。

这里index模块和lib相互依赖。

我们分析一下代码执行过程,首先const {log} = require('./lib.js');导入lib.js模块,这时候开始加载lib模块,lib会先导入index,const {name} = require('./index.js');但这个时候index还没有定义name,所以lib中这里的name是undefined,然后lib导出log方法。

接下来index导出name,然后执行log,由于lib中的name是undefined,因此最终结果是打印undefined。

整个过程模块都是运行时加载,代码依次执行,所以很容易分析出执行结果。

而ES6 module有所不同,接下来看一个es6 module的例子。代码内容和上面一样,只是把模块规范从CommonJS换成es6 module。

// index.mjs
import {log} from './lib.mjs';
export const name = 'index';
log();// lib.mjs
import {name} from './index.mjs';
export const log = () => {console.log(name);
};

首先import {log} from './lib.mjs';导入lib模块,注意这个时候虽然没有执行export const name = 'index';,index模块还没有导出name的值,但是index模块已经编译完成,lib已经可以获取到name的引用,只是还没有值。这非常像代码编译阶段的变量提升。

然后加载lib模块,import {name} from './index.mjs';这句导入了index模块的name,(这时候获取到的是name这个引用,但因为还没有值,因此如果马上打印name console.log(name)会报错。)接下来lib导出log方法。

然后index模块执行export const name = 'index';导出name,其值为"index"。

最后执行log方法log();因为name已经赋值,所以lib中name的引用可以正常访问到值"index",所以最终结果是打印"index"。

综上所述,es6 module虽然模块未初始化好时候就被lib导入,但因为获取的是导出的接口(接口编译阶段就已经输出了),等初始化好之后就能使用了。

2. 引用 VS 值拷贝

ES6 module导入的模块不会缓存值,它是一个引用,这个在上面的例子中已经讨论过。 CommonJS会缓存值,这个很好理解,因为js中普通变量是值的拷贝,其实就是把模块中的值赋给一个新的变量。

看下CommonJS的一个例子

// index.js
const {name} = require('./lib.js'); // 等价于const lib = require('./lib'); const {name} = lib;
setTimeout(() => {console.log(name); // 'Sam'
}, 1000);// lib.js
const lib = {name: 'Sam'
};
module.exports = lib;
setTimeout(() => {lib.name = 'Bob';
}, 500);

index模块中导入lib的nameconst {name} = require('./lib.js');其实就是把lib中的name赋给index里面一个name变量。后面lib中name的变化不会影响到index中的name变量。

而ES6中类似的引用语法,导入的则是引用

// index.mjs
import {name} from './lib.mjs';
setTimeout(() => {console.log(name); // 'Bob'
}, 1000);// lib.mjs
export let name = 'Sam';
setTimeout(() => {name = 'Bob';
}, 500);

这里index模块中的name是lib导出的name的引用,因此lib中name变化会同步到index中。

当然这并不意味着ES6 module可以做到比CommonJS更多的事情,因为如果希望在CommonJS中获取到变化,也可以直接访问lib.name。

// index.js
const lib = require('./lib.js');
setTimeout(() => {console.log(lib.name); // 'Bob'
}, 1000);

所以这个特性的区别只是需要我们在实现模块时候注意一下,避免预期之外的情况。

其实在上面循环引用的例子中,也能看到CommonJS拷贝值和ES6 module引用的区别,CommonJS因为是拷贝值,所以导入模块时候如果还没初始化好,就是undefined,而ES6 module是引用,所以初始化好之后就可以用了。

3. 静态 VS 动态

ES6 module静态语法和CommonJS的动态语法是很重要的区别,

CommonJS的动态性体现在两个方面

  1. 可以根据条件判断是否加载模块
if (condition) {require('./lib');
}
  1. require的模块参数可以是一个变量
require(`./${template}/index.js`);

这种动态性导致依赖关系不好分析,打包工具在静态代码分析阶段不容易知道模块是否需要被加载、模块的哪些部分需要被加载,哪些不会被用到。

相应地,ES6 module的静态性体现在

  1. import必须在顶层
  2. import的模块参数只能是字符串,不能是变量 所以打包工具能够静态分析出依赖关系,并确定知道哪些模块需要被加载、模块的哪些部分被用到。

所以ES6 module静态语法支持打包tree-shaking,而CommonJS不行。

4. 只读 VS 可变

CommonJS导入的模块和普通变量没有区别,ES6 module导入的模块则不同,import导入的模块是只读的。

// demo-commonjs.js
let path = require('path');
path = 1;// demo-esm.js
import path from 'path';
path = 1; // Error: Cannot assign to 'path' because it is an import

5. 异步 VS 同步

ES6 module支持异步加载,浏览器中会用到该特性,而Commonjs是不支持异步的,因为服务器端不需要异步加载。所以CommonJS不可替代ES6 module,ES6 module可以替代CommonJS。

总结

ES6 module和CommonJS的区别主要有5点

  1. ES6 module是编译时导出接口,CommonJS是运行时导出对象。
  2. ES6 module输出的值的引用,CommonJS输出的是一个值的拷贝。
  3. ES6 module语法是静态的,CommonJS语法是动态的。
  4. ES6 module导入模块的是只读的引用,CommonJS导入的是可变的,是一个普通的变量。
  5. ES6 module支持异步,CommonJS不支持异步。

ES6 module作为新的规范,可以替代之前的AMD、CMD、CommonJS作为浏览器和服务端的通用模块方案。NodeJS在13.2.0版本后已经开始完全支持ES6 module,ES6 module在未来也会实际的模块化标准。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/101637.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于STC15单片机-LM35-DS8B20温度测量-DS1302计时-proteus仿真-源程序

一、系统方案 本设计采用STC15单片机作为主控器。 DS18B20采集温度值送到液晶1602显示。 DS1302计时,日期送到液晶1602显示。 LM35采集另一路温度值送到数码管显示。 二、硬件设计 原理图如下: 三、单片机软件设计 1、首先是系统初始化 /IO初始化为…

第 6 章 递归(3)(八皇后问题)

6.7递归-八皇后问题(回溯算法) 6.7.1八皇后问题介绍 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯贝瑟尔于1848年提出:在88格的国际象棋上摆放八个皇后,使其不能互相攻击&#xff0c…

升级还是不升级?iPhone 15和iPhone 14 Plus性能比较

预览iPhone 15 Pro Max与三星Galaxy S23 Ultra之战是有正当理由的。显然,三星的旗舰智能手机为2023年的所有其他旗舰产品定下了基调——由于其超长的电池寿命和一流的摄像头,证明了它是最受欢迎的产品。 毫不奇怪,Galaxy S23 Ultra不仅是最好的照相手机之一,也是花钱能买到…

LlamaGPT -基于Llama 2的自托管类chatgpt聊天机器人

LlamaGPT一个自托管、离线、类似 ChatGPT 的聊天机器人,由 Llama 2 提供支持。100% 私密,不会有任何数据离开你的设备。 推荐:用 NSDT编辑器 快速搭建可编程3D场景 1、如何安装LlamaGPT LlamaGPT可以安装在任何x86或arm64系统上。 首先确保…

商城-学习整理-高级-消息队列(十七)

目录 一、RabbitMQ简介(消息中间件)1、RabbitMQ简介:2、核心概念1、Message2、Publisher3、Exchange4、Queue5、Binding6、Connection7、Channel8、Consumer9、Virtual Host10、Broker 二、一些概念1、异步处理2、应用解耦3、流量控制5、概述 三、Docker安装RabbitM…

Stable Diffusion 系列教程 | 打破模型壁垒

目录 1.模型基本分类 1.1 CheckPoint 大模型/底模型/主模型 1.2 VAE美化模型/变分自编码器 1.3 HyperNetwork 超网络 1.4 embeddings(/Textual Inversion) 嵌入式向量 1.5 loRa 低秩适应模型 2. 下载途径和渠道 2.1 C站 2.1.1 如何筛选到自己需…

【面试高频题】难度 3/5,字典树热门运用题

题目描述 这是 LeetCode 上的 「745. 前缀和后缀搜索」 ,难度为 「困难」。 Tag : 「字典树」 设计一个包含一些单词的特殊词典,并能够通过前缀和后缀来检索单词。 实现 WordFilter 类: WordFilter(string[] words) 使用词典中的单词 words 初…

AutoDev 1.1.3 登场,个性化 AI 辅助:私有化大模型、自主设计 prompt、定义独特规则...

在过去的半个月里,我们为开源辅助编程工具 AutoDev 添加了更强大的自定义能力,现在你可以: 使用自己部署的开源大模型自己配置 Intellij IDEA 中的行为自定义开发过程中的规范 当然了,如果您自身拥有开发能力的话,建议…

StreamingWarehouse的一些思考和未来趋势

300万字!全网最全大数据学习面试社区等你来! 一篇笔记。 以Hudi、Iceberg、Paimon这几个框架为例,它们支持高效的数据流/批读写、数据回溯以及数据更新。具备一些传统的实时和离线数仓不具备的特性,主要有几个方面: 这…

docker 部署服务

1、使用mysql:5.6和 owncloud 镜像,构建一个个人网盘。 [rootbogon ~]# docker pull mysql:5.6 [rootbogon ~]# docker pull owncloud [rootbogon ~]# docker run -itd --name mysql --env MYSQL_ROOT_PASSWORD123456 mysql:5.6 [rootbogon ~]# docker run -itd -…

一文速学-LightGBM模型算法原理以及实现+Python项目实战

LighGBM 前言 LighGBM作为GBDT算法的衍生模型,在其他论文研究以及数学建模比赛中十分常见。如果不熟悉GBDT算法的可以去看看我的上一篇文章,过多关于GBDT的细节不再过多描述。主要将讲述一下LighGBM较于GBDT算法的改进以及独特算法细节优化&#xff0c…

批量爬虫采集完成任务

批量爬虫采集是现代数据获取的重要手段,然而如何高效完成这项任务却是让许多程序员头疼的问题。本文将分享一些实际操作价值高的方法,帮助你提高批量爬虫采集的效率和专业度。 目标明确,任务合理划分: 在开始批量爬虫采集前&…

Python爬虫(十四)_BeautifulSoup4 解析器

CSS选择器:BeautifulSoup4 和lxml一样,Beautiful Soup也是一个HTML/XML的解析器,主要的功能也是如何解析和提取HTML/XML数据。 lxml只会局部遍历,而Beautiful Soup是基于HTML DOM的,会载入整个文档,解析整…

详细介绍如何基于ESP32实现气象站数据显示--附源码

功能介绍: 驱动ili9341 从京东获取天气数据 开始使用 拿到钥匙 1.从京东注册账号 2.从网站获取密钥 安装ESP32 SDK ESP-IDF Programming Guide - ESP32 - — ESP-IDF Programming Guide latest documentation 笔记: 该项目兼容 ESP-IDF 3.X 分支和 4…

【Linux驱动】NVIDIA Jetson Orin NX有时开机启动慢(5~10分钟)

1、问题描述 新到手的 Orin NX 有时开机启动慢,多次测试,总结出规律:在连接网线的情况,启动很慢(5~10分钟);不连接网线的情况下是正常启动速度。 2、原因分析 在连接网线的情况下启动,卡在如下界面很长时间: 可见打印信息: Start HTTP Boot over IPv6. Error: Co…

『论文精读』FastViT(ICCV 2023,Apple开源)论文解读

『论文精读』FastViT(ICCV 2023,Apple开源)论文解读 文章目录 一. FastViT简介二. 模型架构2.1. Stage 的内部架构2.2. Stem 的结构2.3. Patch Embedding 的架构2.4. 位置编码 三. 参考文献 论文下载链接:https://arxiv.org/pdf/2303.14189.pdf论文代码…

BLFS学习系列 第25章. 图形环境库 —— libdrm

一、简介 libdrm提供了一个用户空间库,用于在支持ioctl接口的操作系统上访问直接渲染管理器(DRM)。libdrm是一个低级别库,通常由图形驱动(程序)使用,如Mesa DRI驱动(程序&#xff0…

基于java+swing俄罗斯方块

基于javaswing俄罗斯方块 一、系统介绍二、功能展示三、其他系统实现五、获取源码 一、系统介绍 项目类型:Java SE项目(awtswing)非开源 项目名称:俄罗斯方块(Tertis) 主要技术:java、awt、swing等技术 …

【玩转Linux操作】crond的基本操作

🎊专栏【玩转Linux操作】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题🥰 文章目录 🍔概述🍔命令⭐常用选项 🍔练…

图解算法--排序算法

目录 1.冒泡排序算法 2.选择排序算法 3.插入排序算法 4.希尔排序算法 5.归并排序算法 6.快速排序算法 1.冒泡排序算法 原理讲解: 从待排序的数组中的第一个元素开始,依次比较当前元素和它相邻的下一个元素的大小。如果当前元素大于相邻元素&#x…