当前位置: 首页 > 编程日记 > 正文

require()、import、import()有哪些区别?

require()、import、import()是我们常用的引入模块的三种方式,代码中几乎处处用到。如果对它们存在模糊,就会在工作过程中不断产生困惑,更无法做到对它们的使用挥洒自如。今天我们来一起捋一下,它们之间有哪些区别?

 

一、前世今生

学一个东西,先弄清楚它为什么会出现、它的发展历史、它是做什么的,是很有必要的。看似与技术无关,却很有助于你对技术的理解。

require():

require()是CommonJS引入模块的函数。CommonJS是一种规范,主要用于nodejs,无法用于浏览器端。它是规范,而不是语法。本质上,它是在JS本身不支持模块的情况下,程序员发挥聪明才智,模拟出来的模块系统,在语言层面没有对JS做任何改动。它的原理实际上还是立即执行函数(IIFE)。

import:

JS在设计之初极为简单,没有模块系统,但开发中对模块化的需求却与日俱增。终于,ES6正式在语言标准的层面,引入了模块系统。import就是ES6的模块引入语句,作为ES6本身的语法,只要能写ES6的地方,都能使用它。它最大的特点,是静态引入,在编译时完成模块加载。这带来了加载效率高、静态分析等好处。

require()对比import,类似于VUE之于JS,对比ES6之于ES5的关系。

import():

相比require(),import好处多多,却也丢失了动态引入的优点,即在运行时根据实际需要选择引入模块。怎么办呢?

在ES2020中,引入了import()函数,它和require()一样是动态加载,不同的是,它是异步的,返回一个Promise对象,而require()是同步的。

 

二、缓存方式

(1)先来回答一个面试中常问的问题,一个模块多次引入,会执行几次?

require():

// 2.js
console.log('模块执行开始');
let num = 1;
module.exports = { num };
console.log('模块执行结束');

// testRequire.js
let a = require('./2.js');
let b = require('./2.js');
console.log(typeof a);
console.log(a === b);

// 执行结果
// 模块执行开始
// 模块执行结束
// object
// true

import:

// 1.js
console.log('模块执行开始');
export let num = 1;
console.log('模块执行结束');

// testImport.js
import * as a from './1.js';
import * as b from './1.js';
console.log(typeof a);
console.log(a === b);

// 执行结果
// 模块开始执行
// 模块执行结束
// object
// true

import():

// 1.js
console.log('模块执行开始');
export let num = 1;
console.log('模块执行结束');

// testImportFunction.js
let a = await import('./1.js');
let b = await import('./1.js');
console.log(typeof a);
console.log(a === b);

// 执行结果
// 模块开始执行
// 模块执行结束
// object
// true

由此可见,它们三个,在代码中多次引入同一模块,模块都是只会执行一次。并且,输出结果完全相等(===),也就是指向同一个引用。

我们可以判断,它们都是第一次执行后把输出结果缓存了起来,多次引入不会再次执行,而是直接返回输出结果的引用。

(2)那么,它们对输出结果的缓存方式一样吗?

require():

// 2.js
let num = 1;
let obj = {
	num: 1
};
function add() {
	num += 1;
	obj.num += 1;
}
module.exports = { num, obj, add };

// testRequire.js
let a = require('./2.js');
console.log(a.num); // 1
console.log(a.obj.num); // 1
a.add();
console.log(a.num); // 1
console.log(a.obj.num); // 2

require的缓存方式,是对输出结果进行拷贝,而且是浅拷贝。值类型直接拷贝,引用类型拷贝内存地址。

import:

// 1.js
let num = 1;
let obj = {
	num: 1
};
function add() {
	num += 1;
	obj.num += 1;
}
export { num, obj, add };

// testImport.js
import * as a from './1.js';
console.log(a.num); // 1
console.log(a.obj.num); // 1
a.add();
console.log(a.num); // 2
console.log(a.obj.num); // 2

import并不对输出结果进行拷贝,而是直接指向输出结果的引用。

import():

// 1.js
let num = 1;
let obj = {
	num: 1
};
function add() {
	num += 1;
	obj.num += 1;
}
export { num, obj, add };

// testImportFunction.js
let a = await import('./1.js');
console.log(a.num); // 1
console.log(a.obj.num); // 1
a.add();
console.log(a.num); // 2
console.log(a.obj.num); // 2

import()也是一样的,直接指向输出结果的引用。

 

三、静态?动态?

静态引入:

所谓静态引入,就是在编译时引入,那么就不能使用在运行时才能得到结果的语法结构了,比如不能包在if语句里,引用路径不能使变量和表达式,要求必须是字符串字面量。

import就是静态引入。

动态引入:

动态引入,就是在运行时引入。因此可以根据条件判断来按需引入,引用路径也可以写成变量或表达式。

require()和import()都是动态引入。

 

四、同步?异步?

require():

// 2.js
console.log('模块执行开始');
let num = 1;
for (var i = 0; i < 1000000000; i++) {

}
module.exports = { num };
console.log('模块执行结束');

// testRequire.js
let a = require('./2.js');
console.log('执行其他代码');

// 执行结果
// 模块执行开始
// 若干秒后...
// 模块执行结束
// 执行其他代码

require()引入模块,是同步的,但是因为是在服务端本地引用,同步引入完全没有问题。

import:

// 1.js
console.log('模块执行开始');
let num = 1;
await new Promise(resolve => {
	setTimeout(resolve, 3000);
});
export { num };
console.log('模块执行结束');

// testImport.js
import * as a from './1.js';
console.log('执行其他代码');

// 执行结果
// 模块执行开始
// 3秒后...
// 模块执行结束
// 执行其他代码

这儿让我非常意外,也很困惑。都说import是异步引入的,为什么这儿的结果却显示它是同步的?哪位能解答我的疑惑?

import():

// 1.js
console.log('模块执行开始');
let num = 1;
await new Promise(resolve => {
	setTimeout(resolve, 3000);
});
export { num };
console.log('模块执行结束');

// testImportFunction.js
import('./1.js');
console.log('执行其他代码');

// 执行结果
// 执行其他代码
// 模块执行开始
// 3秒后...
// 模块执行结束

没问题,import()是异步引入,返回的是一个Promise对象。

 

五、相互引用

require():

require()无法引入ES6模块,但可以使用import()函数来引入ES6模块。

import:

// 2.js
let num = 1;
let obj = {
	num: 1
};
module.exports = { num, obj, add };

// testImport.js
import a from './2.js';
console.log(a.num); // 1
console.log(a.obj.num); // 1

import可以引入CommonJS模块,是把module.exports对象整体引入,类似于对exports default的接收,直接用一个变量来接收。

import():

// 2.js
let num = 1;
let obj = {
	num: 1
};
module.exports = { num, obj, add };

// testImportFunction.js
let a = await import('./2.js');
console.log(a.default.num); // 1
console.log(a.default.obj.num); // 1

import()整体接收module.exports这个对象,并把它放在default属性下。

 

就总结这些吧。本人水平非常有限,写作主要是为了把自己学过的东西捋清楚。如有错误,还请指正,感激不尽。

相关文章:

如何保护电动汽车充电站免受网络攻击

文章浏览阅读228次。从电动汽车、传感器、充电站和支持基础设施的设计阶段开始就强调安全性作为首要任务。

Vite4+Typescript+Vue3+Pinia 从零搭建(3) - vite配置

项目代码同步至码云 weiz-vue3-template 关于vite的详细配置可查看 vite官方文档,本文简单介绍vite的常用配置。 初始内容 项目初建后,vite.config.ts 的默认内容如下: import { defineConfig } from &#39;vite&#39; i

原型模式 rust和java的实现

文章浏览阅读218次。意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。主要解决:在运行期建立和删除原型。何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。3、为了避免创建一个与产品类层次平行的工厂类层次时。4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。关键代码。

c语言const修饰变量与assert断言详解

const修饰变量与assert断言详解,const修饰变量。作用:const用于修饰变量使其不能再被修改。

Linux如何修改主机名(hostname)(亲测可用)

文章浏览阅读1k次。要想在虚拟机的 Linux 系统内部改变主机名(hostname),需要通过系统的配置来修改。文件,将其中引用旧主机名的条目更新为新主机名。文件,并将里面的内容替换为新主机名。但是大多数情况可能无需更改,除非在。文件里做了什么硬编码骚操作🤣。替换为想要设置的新主机名。或者使用文本编辑器手动编辑。需要重新设置主机名。

Linux下内网穿透实现云原生观测分析工具的远程访问

夜莺监控是一款开源云原生观测分析工具,采用 All-in-One 的设计理念,集数据采集、可视化、监控告警、数据分析于一体,与云原生生态紧密集成,提供开箱即用的企业级监控分析和告警能力。夜莺于 2020 年 3 月 20 日,在 github 上发布 v1 版本,已累计迭代 100 多个版本。本地部署后,为解决无法远程访问的难题,今天我们介绍如何实现让本地nightingale 结合cpolar 内网穿透工具实现 远程也可以访问,提高运维效率.

Java Lambda 表达式笔记

文章浏览阅读71次。Lambda 表达式,也可称为闭包.Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

JavaScript中如何终止forEach循环?

在JavaScript中,forEach方法是用于遍历数组的,通常没有直接终止循环的机制。然而,我们可以使用一些技巧来模拟终止forEach循环。以下是几种常见的方法

Docker本地部署Drupal并实现公网访问

文章浏览阅读498次,点赞35次,收藏33次。Dupal是一个强大的CMS,适用于各种不同的网站项目,从小型个人博客到大型企业级门户网站。它的学习曲线可能相对较陡,但一旦熟悉了它的工作方式,用户就能够充分利用其功能和灵活性。在本文中,我们将介绍如何使用Docker快速部署Drupal,并且结合cpolar内网穿透工具实现公网远程访问首先,您需要在您的机器上安装Docker,并且启动,可以按照Docker官方文档中的说明进行安装。

如何使用 RestTemplate 进行 Spring Boot 微服务通信示例

概述 下面我们将学习如何创建多个 Spring boot 微服务以及如何使用 RestTemplate 类在多个微服务之间进行同步通信。 微服务通信有两种风格: 同步通讯 异步通信 同步通讯 在同步通信的情况下,客户端发送请求并等待服务的响应。这里重要的一点是协议(HTTP/HTTPS)是同步的,客

win10 通过wmic命令行设置系统环境变量

而通过编程修改系统环境变量,需要调用注册表API或调用wmi API接口,都有些过于麻烦。此时,如果通过system函数,直接调用批处理文件,则只需要一行代码。批处理中,分别给出了创建环境变量,修改环境变量,删除环境变量的demo。可以根据需要调整批处理文件。在系统维护或编写程序过程中,经常需要对系统环境变量进行设置、修改、删除炒作。注:修改系统环境变量,需要有管理员权限。

c#操作mongodb数据库工具类

新建c#项目,在nuget中引入MongoDB.Driver驱动,然后新建一个MongoDBToolHelper类,将下面代码复制进去 using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver; using

Redis查看集群状态有节点显示fail,连接失败

现有6台redis,为集群,3主3从,由于两台服务器故障重装系统之后进行重新安装2台redis,安装完成之后。查询当前redis状态,发现有存留的节点,但连接为fai。将新建的两台redis启动,保证配置文件一致。查询fail节点的node_id。将新建的两台redis设为从节点。登录新建的两台redis服务器。先通过ID删掉无用的节点。

SpringBoot整合Kafka (二)

Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景,比如基于hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源 项目。

SpringBoot整合Kafka (一)

Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景,比如基于hadoop的批处理系统、低延迟的实时系统、Storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源 项目。

深入详解高性能消息队列中间件 RabbitMQ

在进行系统设计的时候,各个模块、服务器之间为了实现数据的交互,通常是建立连接通过发送消息来进行。如果将他们一一建立连接,就会出现链路太多,每一条链路都必须感知对端等问题。此场景下消息将非常混乱,后期维护也将非常痛苦。

ModuleNotFoundError: No module named ‘config‘

当你在Python代码中看到类似"ModuleNotFoundError: No module named 'config'"的错误消息时,意味着你尝试导入一个名为'config'的模块,但Python无法找到该模块

【云原生基础】了解云原生,什么是云原生?

云原生(Cloud-Native)是一种软件开发和部署方法论,旨在利用云计算、容器化、微服务架构和持续交付等现代技术和最佳实践,以构建、部署和运行可伸缩、高可用、弹性和易于管理的应用程序。云原生应用程序旨在充分发挥云计算的潜力,以更好地满足快速发展的数字业务需求。容器化:云原生应用程序通常使用容器技术(如Docker)进行封装,使应用程序及其依赖项可以在不同环境中一致运行。容器化提供了隔离、可重复部署和快速部署的好处。

【微服务】mysql + elasticsearch数据双写设计与实现

在很多电商网站中,对商品的搜索要求很高,主要体现在页面快速响应搜索结果。这就对服务端接口响应速度提出了很高的要求。

Python异常处理:try、except、else 和 finally 的使用指南

Python异常处理:try、except、else 和 finally 的使用指南

HarmonyOS 数据管理与应用数据持久化(二)

文章浏览阅读31次。关系型数据库基于 SQLite 组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。

HarmonyOS数据管理与应用数据持久化(一)

文章浏览阅读599次。一.

Ubuntu系统下怎么安装Docker(linux安装docker教程)

更新系统软件包 在安装 Docker 前,首先需要更新系统软件包,确保系统上的软件都是最新的版本

python实战讲解之使用Python批量发送个性化邮件

通过上述Python脚本,我们可以批量发送个性化的邮件。我们首先设置发件人邮箱和密码,然后指定SMTP服务器和端口号。接下来,我们读取包含员工信息的Excel文件,并获取唯一的员工姓名列表和对应的邮箱地址。然后,我们遍历员工数据,并为每个员工创建邮件,附带相应的附件。最后,我们通过SMTP服务器发送邮件,并在发送完成后删除生成的员工数据文件。

什么是加密?浅谈MD5加密

文章浏览阅读108次。加密解密是一种通过使用密码算法对信息进行转换,以使其在传输或存储过程中变得不可读或难以理解,从而保护信息的安全性和隐私性的过程。加密是将明文(原始文本)转换为密文(加密后的文本)的过程。在加密过程中,使用密钥和特定的算法来改变原始文本的形式,使其在未经授权的情况下无法理解。只有持有正确密钥的人才解密是将密文转换回明文的过程。数字摘要算法(Digital Digest Algorithm)是一种加密算法,用于将任意长度的数据转换为固定长度的摘要(也称为哈希值)。

Python自动化处理Excel数据

需求描述:数据格式如下所示,需要分离出2023年7月1号之后的数据明细 数据核对与处理:从Excel文件中提取特定日期后的签收数据 1. 引言 在实际数据处理和分析过程中,经常会遇到需要从大量数据中提取出特定日期范围内的信息的需求。本文将介绍如何使用Python的pandas库来处理Excel文件,

Linux 环境下 安装 Elasticsearch 7.13.2

文章浏览阅读42次。借公司的 centos 7 服务器,搭建一个 Es,正好熟悉熟悉 Linux 下的安装流程。

基于 MySQL 多通道主主复制的机房容灾方案

文章中介绍了多种 MySQL 高可用技术,并介绍了根据自身需求选择多通道主主复制技术的过程和注意事项。

C#利用一段字节序列构建一个数组对象

《.NET中的数组在内存中如何布局? 》介绍了一个.NET下针对数组对象的内存布局。既然我们知道了内存布局,我们自然可以按照这个布局规则创建一段字节序列来表示一个数组对象,就像《以纯二进制的形式在内存中绘制一个对象》构建一个普通的对象,以及《你知道.NET的字符串在内存中是如何存储的吗?》构建一个字

【卷积神经网络】YOLO 算法原理

文章浏览阅读128次。在计算机视觉领域中,目标检测(Object Detection)是一个具有挑战性且重要的新兴研究方向。目标检测不仅要预测图片中是否包含待检测的目标,还需要在图片中指出它们的位置。2015年,Joseph Redmon, Santosh Divvala 等人提出第一个 YOLO 模型,该模型具有实时性高、支持多物体检测的特点,已成为目标检测领域热门的研究算法。本文主要介绍 YOLO 算法及其基本原理。