Commonjs和Es6语法规范的理解

news2025/1/12 18:01:11

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.coloradmin.cn/o/915448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle-rolling upgrade升级19c

前言: 本文主要描述Oracle11g升19c rolling upgrade升级测试,通过逻辑DGautoupgrade方式实现rolling upgrade,从而达到在较少停机时间内完成Oracle11g升级到19c的目标 升级介绍: 升级技术: rolling upgrade轮询升级,通过采用跨版…

PS gif修改背景颜色(附加图片)

文章目录 问题描述.mp4转换为.gif 解决方案gif更换背景修改背景具体操作第一步第二步第三步第四步第五步第六步 总结 大家好!不知不觉又到周二了,农历:七月七,本来今天可以高高兴兴的度过一天,唉,接到领导布…

强训第38天

选择 D 0作为本地宿主机&#xff0c;127作为内部回送&#xff0c;不予分配 A B C C 存储在浏览器 D A B B D 网络延迟是指从报文开始进入网络到它离开网络之间的时间 编程 红与黑 红与黑__牛客网 #include <iostream> #include <stdexcept> #include <string…

【仿写框架之仿写Tomact】一、详解Tomcat的工作流程

文章目录 1、启动阶段2、监听阶段&#xff1a;3、请求处理阶段&#xff1a;4、发送请求处理后的响应 当涉及到Java Web应用程序的部署和运行&#xff0c;Apache Tomcat无疑是一个备受欢迎的选择。Tomcat作为一个开源的、轻量级的Java Servlet容器和JavaServer Pages (JSP) 容器…

排兵布阵很重要

以正合&#xff0c;以奇胜&#xff0c;排兵布阵很重要 【安志强趣讲《孙子兵法》第17讲】 第五章&#xff1a;势篇 【全文趣讲大白话】 善于打仗&#xff0c;能恰当的分兵&#xff0c;能借势。 【原文】 孙子曰&#xff1a;凡治众如治寡&#xff0c;分数是也&#xff1b;斗众如…

SFL214-100-21-10喷嘴挡板伺服阀

SFL214-100-21-10、SFL214-150-21-15、SFL214-100-32-40、SFL214-100-21-40、SFL216-230-21-40、SFL216-230-32-40采用千式力马达和两级液压放大器结构前置级为无摩撩副的双喷嘴挡板阀性能优良&#xff0c;动态响应高适用于对位置&#xff0c;力和速度进行闭环控制可用作三通阀…

函数的使用-1函数默认参数

在C中&#xff0c;函数的形参列表中的形参是可以有默认值的。 语法&#xff1a; 返回值类型 函数名 &#xff08;参数 默认值&#xff09;{} 1. 如果某个位置参数有默认值&#xff0c;那么从这个位置往后&#xff0c;从左向右&#xff0c;必须都要有默认值 2. 如果函数声明有默…

FPGA实现10G万兆网TCP/IP 协议栈,纯VHDL代码编写,提供服务器和客户端2套工程源码和技术支持

目录 1、前言免责声明 2、我这里已有的以太网方案3、该TCP/IP协议栈性能常规性能支持多节点FPGA资源占用少数据吞吐率高低延时性能 4、TCP/IP 协议栈代码详解代码架构用户接口代码模块级细讲顶层模块PACKET_PARSING_10G模块ARP_10G模块DHCP_SERVER_10G和 DHCP_CLIENT_10G模块IG…

c语言实现双向链表

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、链表的分类1、 单向或者双向链表2、带头结点或者不带头结点的链表3、循环或者非循环链表 二、带头双向循环链表1、链表结点的结构体定义2、链表的初始化3、…

交换机的基本原理与配置(三)

目录 3. 交换机基本配置 3.1 交换机配置前的连接 3.2 Cisco交换机的命令行模式 3.3 交换机的常见命令 3. 交换机基本配置 交换机的基本配置介绍如下。 3.1 交换机配置前的连接 配置一台Cisco交换机的方法有多种&#xff0c;本节介绍通过Console(控制台)接口讲行配置的方式这…

PCIE超高速实时运动控制卡在六面外观视觉检测上的应用

市场应用背景 XPCIE1028超高速实时运动控制卡在六面外观检测高速视觉筛选中的应用&#xff0c;结合正运动技术提供的专用筛选机调试软件&#xff0c;可实现15000pcs/分钟的IO触发检测速度&#xff0c;只需简单参数设置&#xff0c;搭配图像采集硬件和视觉处理软件&#xff0c;…

交换机的基本原理与配置(一)

目录 简介 1. 数据链路层 1.1 数据链路层的功能 1.2 以太网的由来 1.3 以太网帧格式 简介 在讲解OSI参考模型的章节中&#xff0c;我们已经对以太网数据单元有了初步的认识&#xff0c;本章将在此基础 上进一步学习数据链路层的主要内容&#xff0c;并首次接触网络中的一个…

电商平台按关键字搜索商品淘宝京东拼多多api接口PHP示例

关键词搜索商品接口的作用是通过调用接口来实现在电商平台中进行商品搜索。具体而言&#xff0c;该接口可以提供以下功能和作用&#xff1a; 商品搜索&#xff1a;用户可以通过输入关键词&#xff0c;在电商平台上进行商品搜索。接口可以根据关键词对商品的名称、描述、标签等…

Mybatis动态之灵活使用

目录 ​编辑 1.MyBatis中的动态SQL是什么&#xff1f; 2.MyBatis中的动态SQL作用 3.代码演示 4. #和 $使用 2.1 #使用 ( 1 ) #占位符语法 ( 2 ) #优点#占位符语法在使用动态SQL时具有以下优点&#xff1a; 2.2 $使用 ( 1 ) $占位符语法 ( 2 ) $优点$占位符语法在使用动…

RabbitMQ 消费者

RabbitMQ的消费模式分两种&#xff1a;推模式和拉模式&#xff0c;推模式采用Basic.Consume进行消费&#xff0c;拉模式则是调用Basic.Get进行消费。   消费者通过订阅队列从RabbitMQ中获取消息进行消费&#xff0c;为避免消息丢失可采用消费确认机制 消费者 拉模式拉模式的实…

ChatGPT应用于高职教育的四大潜在风险

目前&#xff0c;ChatGPT还是一种仍未成熟的技术&#xff0c;当其介入高职教育生态后&#xff0c;高职院校师生在享受ChatGPT带来的便利的同时&#xff0c;也应该明白ChatGPT引发的风险也会随之进入高职教育领域&#xff0c;如存在知识信息、伦理意识与学生主体方面的风险与挑战…

轻松正确使用代理IP

Hey&#xff0c;亲爱的程序员小伙伴们&#xff01;在进行爬虫时&#xff0c;你是否曾使用过别人的代理IP&#xff1f;是否因此慌乱&#xff0c;担心涉及违法问题&#xff1f;不要惊慌&#xff01;今天我将和你一起揭开法律迷雾&#xff0c;为你的爬虫之路保驾护航。快跟上我的节…

C++核心编程——类和对象(二)、友元、多态、文件操作

C对象模型和this指针 4.3.1 成员变量和成员函数分开存储 在C中&#xff0c;类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 空类&#xff08;类里面是空的&#xff09;&#xff0c;空对象占用内存空间为&#xff1a;1字节。 静态成员变量&#xff0…

简单屏幕共享 通过web screego windows 生成证书

生成证书用 linux 生成&#xff0c;在 windows 下使用 windows 生成证书 https://juejin.cn/post/6925006735933440014 下载地址 https://github.com/screego/server/releases 修改完配置后&#xff0c;运行 screego serve 需要修改的几个地方 # 局域网 ip 或公网 ip&…