通过示例详细了解ES6导入导出模块

news2024/11/20 4:46:45

通过示例详细了解ES6导入导出模块

似乎许多开发人员认为 ES6 模块只不过是exportimport关键字。事实上,它更加多样化。它拥有强大的功能和鲜为人知的问题。在本文中,我们将使用一些示例来了解这些内容。

示例一

// index.mjs
import { default } from './module.mjs';

console.log(default);
// module.mjs
export default 'bar';

首先,让我们记住各种导入和导出语法:

导出:
在这里插入图片描述
导入:

在这里插入图片描述
如果我们对照上面的import语法,会看到没有与我们的代码匹配的语法:

import { default } from ‘./module.mjs’;

因为这个语法是禁止的。测试代码抛出以下错误:

SyntaxError: Unexpected reserved word

import { default } from ‘./module.mjs’;这张代码中,导出的名字是default,在这个上下文中的变量名也是default,但是default 是个保留词,不能这样使用。解决这个问题很容易:

import { default as foo } from ‘./module.mjs’;

现在,导出的名字是default,上下文中的变量名改成foo。换句话说,如果我们想为默认导出使用指定的导入语法,则必须重命名它。

示例二

// index.js
console.log('index.js');

import { sum } from './helper.js';

console.log(sum(1, 2));
// help.js
console.log('helper.js');

export const sum = (x, y) => x + y;

很少有开发者知道的重要细微之处是import被提升了。也就是说,当引擎正在解析代码时,它们就会上升。在代码运行之前,将加载所有依赖项。

打印的日志为:

helper.js
index.js
3

如果我们希望在导入声明之前执行一些代码,请考虑将其移到一个单独的文件中:

import './logs.js';
import { sum } from './helper.js';

console.log(sum(1, 2));
// logs.js
console.log('index.js');

打印顺序为:

index.js
helper.js
3

示例三

// index.mjs
import './module.mjs';
import { num } from './counter.mjs';

console.log('index num =', num);
// index num = 1;
// module.mjs
import { num } from './counter.mjs';

console.log('module num =', num);
// module num = 1;
// counter.mjs
if (!globalThis.num) {
  globalThis.num = 0;
}

export const num = ++globalThis.num;

模块是单例的:

无论我们从同一文件或不同文件导入模块多少次,该模块只能执行和加载一次。换句话说,只有一个模块实例。

示例四

// index.mjs
import './module.mjs?param=5;'
// module.mjs
console.log(import.meta.url);

根据MDN :

重要的是。元对象将上下文特定的元数据公开给一个JavaScript模块。它包含关于模块的信息。
它返回一个具有URL属性的对象,该属性指示URL模块的。这将是获取脚本的URL,用于外部脚本,或者是包含文档的文档URL,用于内联脚本。

请注意,这将包括查询参数和/#(即跟着?或者#)。

示例五

import myCounter from './counter';

myCounter += 1;

console.log(myCounter);
// counter.js
let counter = 5;
export default counter;

大多数开发人员忽略的另一个极其重要部分是导入像常量这样变量的时候,不能直接修改这个变量值(修改的值只在当前文件生效,不会更新到模块中),为了使代码有效,可以导出对象并更改其属性。

示例六

// index.mjs
import foo from './module.mjs';

console.log(typeof foo);
// number
// module.mjs
foo = 25;

export default function foo() {}

首先,这个

export default function foo() {}

等于

function foo() {}
export { foo as default }

也等于

function foo() {}
export default foo

但一开始的打印并不是function 而是number。别惊讶,继续读。我们下面会继续讲。

现在是时候记住了,函数声明会被提升,变量的初始化总是在函数/变量声明之后进行的。

引擎处理完模块代码后,看起来是这样的:

function foo() {}

foo = 25;

export { foo as default }

因此,打印的结果是number

示例七


// index.mjs
import defaultFoo, { foo } from './module.mjs';

setTimeout(() => {
  console.log(foo);
  console.log(defaultFoo);
}, 2000);
// module.mjs
let foo = 'bar';

export { foo };
export default foo;

setTimeout(() => {
  foo = 'baz';
}, 1000);

在大多数情况下,export数据是活的。也就是说,如果导出值发生了变化,则此变化会反映在导入变量中。

但对export default而言并非如此:

export default foo;

当使用此语法时,不是导出变量,而是它的值。我们可以使用以下语法导出默认值:

export default ‘hello’;
export default 42;

如果我们查看示例一的export语法,会发现export default function () {} 在另一个列(Default export)而不是export default foo (Export of values).

这是因为它们的行为不同,函数仍然作为实时引用传递:

  // module.mjs
  export { foo };

  export default function foo() {};

  setTimeout(() => {
    foo = 'baz';
  }, 1000);
  // index.mjs
  import defaultFoo, { foo } from './module.mjs';

  setTimeout(() => {
    console.log(foo); // baz
    console.log(defaultFoo); //baz
  }, 2000);

让我们再看一下示例一中的export表。

export { foo as default }; 是在Named Export内,跟之前示例中不同。但对我们来说,唯一重要的是Export of values 部分。因此,这意味着当我们以这种方式导出数据时,它将是对导入值的活绑定。

示例八


// index.mjs
import { shouldLoad } from './module1.mjs';

let num = 0;

if (shouldLoad) {
  import { num } from './module2.mjs';
}

console.log(num);
// module1.mjs
export const shouldLoad = true;

//module2.mjs
export const num = 1;

import { num } from ‘./module2.mjs’; 这行代码会抛出一个错误,因为import构造必须在脚本的最高层:

SyntaxError: Unexpected token ‘{‘

这是一个重要的限制,它加上在文件路径中使用变量的限制,使得ES6模块成为静态的。这意味着我们不需要执行代码来找出所有模块之间的依赖关系,这一点与普通模块不同。

在这个例子中,使用common.js模块,找出哪个模块 ab 将加载,需要运行以下代码:


let result;
if (foo()) {
    result = require('a');
} else {
    result = require('b');
}

模块的静态特性有很多好处,其中一些是:

  1. 我们总是知道导入数据的确切结构。这有助于指针在执行代码之前找到错误。
  2. 异步加载。这是因为模块是静态的,在执行模块体之前可以加载导入。
  3. 支持循环依赖关系。我们将在下一个示例中更详细地探讨这种可能性。
  4. 有效的捆绑。这个暂时不多讲,之后会出一篇文章单独讲解

ES6中,如果需要有条件地加载模块,可以使用import() 功能式结构。

示例九


// index.mjs
import { double, square } from './module.mjs';

export function calculate(value) {
  return value % 2 ? square(value) : double(value);
}

// module.mjs
import { calculate } from './index.mjs';

export function double(num) {
  return num * 2;
}

export function square(num) {
  return num * num;
}

console.log(calculate(3));

在上面的代码中,我们可以看到循环依赖关系:index.mjs 引入 module.mjs模块的doublesquare方法 ,而module.mjs又引入了index.mjs模块的calculation方法。

这个代码之所以有效,是因为ES6模块本质上支持循环依赖关系。例如,如果我们使用cjs来改写这个代码它将不再工作:

// index.js
const helpers = require('./module.js');

function calculate(value) {
  return value % 2 ? helpers.square(value) : helpers.double(value);
}

module.exports = {
  calculate
}
// module.js
const actions = require('./index.js');

function double(num) {
  return num * 2;
}

function square(num) {
  return num * num;
}

console.log(actions.calculate(3));
// TypeError: actions.calculate is not a function

module.exports = {
  double,
  square
}

这是nodejs中的常见问题,让我们看看这个代码是如何工作的:

  1. 开始加载index.js
  2. 加载会在第一行中加载module.js时中断
    const helpers = require(./module.js’);
    
  3. 开始加载module.js
  4. console.log(actions.calculate(3));这行代码引发错误是因为actions.calculate 没有定义。这是因为JS同步加载模块。index.js 它的导出对象目前是空的。

如果延迟调用导入函数,则index.js 模块将有时间加载:

// module.js
const actions = require('./index.js');

function double(num) {
  return num * 2;
}

function square(num) {
  return num * num;
}

function someFunctionToCallLater() {
  console.log(actions.calculate(3)); // Works
}

module.exports = {
  double,
  square
}

正如我们在上一示例中所知道的,es6模块支持循环依赖关系,因为它们是静态的–模块的依赖关系在代码执行之前就被加载了。

另一个使上述代码工作的东西是函数提升。当calculate 函数被调用时,我们还没有到calculate 函数定义的那一行:

以下是在模块打包后代码的样子:

function double(num) {
  return num * 2;
}

function square(num) {
  return num * num;
}

console.log(calculate(3));

function calculate(value) {
  return value % 2 ? square(value) : double(value);
}

如果没有函数提升这段代码就不能正常工作。

如果我们将函数声明改为函数表达式:

export let calculate = function(value) {
  return value % 2 ? square(value) : double(value);
}

它会造成以下错误:

ReferenceError: Cannot access ‘calculate’ before initialization

示例十

// index.mjs
import { num } from './module.mjs';

console.log(num);
export let num = 0;

num = await new Promise((resolve) => {
  setTimeout(() => resolve(1), 1000);
});

Top-level await这是一个非常有用的功能,许多开发人员不知道,也许是因为它是最近在ECMASKIPT2022引入的。

根据 tc39 top-level await proposal :

Top-level await 使得模块可以在异步函数中发挥非常大的作用,在Top-level await 的情况下,ECMAScript模块(esm)可以等待资源,导致其他导入模块的部分需要等待完成后才能进行解析。

模块的标准行为是:在它导入的所有模块都被加载并执行它们的代码之前,模块中的代码不会被执行(参考示例二)。事实上,Top-level await 也没有改变这个行为。模块中的代码在导入模块中的所有代码被执行之前才会执行,只是现在包括等待模块中所有期待的promise被解决。

console.log('index.js');

import { num } from './module.js';

console.log('num = ', num);
export let num = 5;

console.log('module.js');

await new Promise((resolve) => {
  setTimeout(() => {
    console.log('module.js: promise 1');
    num = 10;
    resolve();
  }, 1000);
});

await new Promise((resolve) => {
  setTimeout(() => {
    console.log('module.js: promise 2');
    num = 20;
    resolve();
  }, 2000);
});

打印:

module.js
module.js: promise 1
module.js: promise 2
index.js
num = 20

如果我们在module.js中删除第5和第13行,并在index.js文件中添加timeout,像这样:

console.log('index.js');

import { num } from './module.js';

console.log('num = ', num);

setTimeout(() => {
  console.log('timeout num = ', num);
}, 1000);

setTimeout(() => {
  console.log('timeout num = ', num);
}, 2000);

打印:

module.js
index.js
num = 5
module.js: promise 1
timeout num = 10
module.js: promise 2
timeout num = 20

示例十一


import { shouldLoad } from './module1.mjs';

let num = 0;

if (shouldLoad) {
   ({ num } = import('./module2.mjs'));
}

console.log(num);
// module1.mjs
export const shouldLoad = true;
//module2.mjs
export const num = 1;

根据MDN :

调用import(),通常称为动态导入,是一个类似函数的表达式。允许异步和动态加载一个ECMAScript模块。它允许规避导入声明的语法刚度,并有条件地或根据需要加载模块。

这一功能是在ES2020中引入的。

import(module)返回实现包含模块所有导出的对象的一个Promise

import调用之前添加await关键词

if (shouldLoad) {
   ({ num } = await import('./module2.mjs'));
}

在这里我们再次使用Top-level await

在尝试从全局范围调用异步函数时经常会发生下面这种情况:
SyntaxError: await is only valid in async functions

为了解决这个问题,我们可以使用:

(async () => {
  await [someAsyncFunc]();
})();

这段代码看起来就丑,而且在异步中使用此模式加载模块时可能会导致错误。例如:

// module1.mjs
let num;
(async () => {
	({ num } = await import(./module2.mjs’));
})();

export { num };

// module2.mjs
export const num = 5;

当导入module1.mjs 模块时num的值是什么,是来自module2还是undefined?这将取决于变量何时被访问:


import { num } from './module1.mjs';

console.log(num); // undefined
setTimeout(() => console.log(num), 100); // 5

当我们导入一个具有Top-level await的模块时(module1),num的值永远都不会是undefined

let { num } = await import('./module2.mjs');

export { num };
import { num } from './module1.mjs';

console.log(num); // 5

示例十二

const module1 = await import('./module1.mjs');
const module2 = await import('./module2.mjs');

console.log(module1, module2);

function multiply(num1, num2) { return num1 * num2; }

console.log(multiply(module1, module2));
// module1.mjs
export default await new Promise((resolve) => resolve(1));
// module2.mjs
export default await new Promise((resolve) => resolve(2));

上面的代码会造成一个错误:

TypeError: Cannot convert object to primitive value

让我们找出这个错误的来源。

在这段代码中,我们使用了一个动态导入,我们在前面的示例中已经遇到了它。为了理解代码中的问题,我们需要更仔细地研究import()

变量module1module2 不是我们期望那样的值。import() 返回一个具备和命名导入相同字段的已解决的promise

import * as name from moduleName

default导出一个键名为default的对象。

所以返回值不是1 和2,而是{ default: 1 }{ default: 2 }

为什么我们在用两个对象相乘时会有这样一个奇怪的错误,而不是NaN

这是因为返回的对象有一个null 原型。因此,它没有toString() 方法(用于将对象转换为字符)。如果这个对象有Object 原型,我们才会看到NaN

为了修正测试代码,我们需要做以下修改:

console.log(multiply(module1.default, module2.default));

或者


const { default: module1 } = await import('./module1.mjs');
const { default: module2 } = await import('./module2.mjs');

示例十三


// index.js
import * as values from './intermediate.js';

console.log(values.x);
// module1.js
export const x = 1;
// module2.js
export const x = 2;
// intermediate.js
export * from './module1.js';
export * from './module2.js';

export * from ‘module’ 会重新导出所有从module模块导出name exports并作为当前文件的name exports,如果有重名的情况,那么都不是重导出。

所以运行这个代码在控制台里会打印undefined

另外,如果在同样的情况下引入x ,如预期的那样,我们会有一个错误:

import { x } from ‘./intermediate.js’;

SyntaxError: The requested module ‘./intermediate.js’ contains conflicting star exports for name ‘x’

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

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

相关文章

【STL】容器适配器stack和queue常见用法及模拟实现

目录 1.stack介绍及使用1.1 stack的介绍1.2 stack使用 2. stack模拟实现3. queue介绍及使用3.1 queue的介绍4. queue模拟实现 5. 栈和队列使用不同默认适配器的区别6. dequeue原理简单介绍6.1 dequeue底层实现6.2 dequeue的缺点 1.stack介绍及使用 1.1 stack的介绍 stack文档介…

每天debug/run一键启动的Spring Boot控制台启动日志,你了解过吗?

文章目录 前言JDK执行Spring Boot应用的启动命令Spring Boot本身启动时的日志总结 前言 每次打开Idea点击debug/run启动SpringBoot项目时,都在坐等启动成功的最后一行日志,然而对于启动过程中,控制台里面的每一行日志代表什么?你…

论文学习——FALL-E:GAUDIO FOLEY SYNTHESIS SYSTEM

文章目录 引言正文AbstractIntroduction介绍问题 FALL-E2.1 Architexture结构2.2 Training and Inference Details 3 Evaluation And Analysis测试和分析Conlusion 总结 引言 这篇文章是DCASE中少有的,没有使用DIffusion的方法,可以学习一下。这篇文章的…

『C++之STL』双端队列 - deque

前言 双端队列,Double-ended queue,简称为deque是一种线性结构的一种容器; 在数据结构中出现的顺序表与链表,或者栈与队列都算是线性结构; 在结构中,它与vector相比较会相似一些; 但是在实际当中,双端队列 - deque 包含了vector与list的优点; vector(顺序表) 支持随机访问,空…

cuda12+vs2019环境搭建 发疯实录

点击exe文件后开始安装(注意更改默认安装的位置) 在选项阶段,全选所有的选项 出现的问题,这里显示未安装 进一步地查看原因 可能式对应的版本下载错误 如何寻找到所需要的版本并进行下载? 在上述参考链接中进行搜…

阿里云韩国服务器测试IP地址及公网带宽收费价格表

阿里云服务器韩国(首尔)地域公网带宽价格表,1M带宽价格是23.0元/月,按使用流量1GB价格是0.8元,阿里云韩国服务器测试IP地址:149.129.12.20,阿里云百科aliyunbaike.com来详细说下阿里云韩国服务器…

mybatis用拦截器实现字段加解密

前言 根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用mybatis拦截器进行简单实现个demo。 拦截器的使用 // 执行 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) // 请求参数处理 Paramete…

基于蛾群优化的BP神经网络(分类应用) - 附代码

基于蛾群优化的BP神经网络(分类应用) - 附代码 文章目录 基于蛾群优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.蛾群优化BP神经网络3.1 BP神经网络参数设置3.2 蛾群算法应用 4.测试结果:5.M…

常用的数字格式代码

文章目录 数值占位符文本占位符 两类占位符: 数值占位符, 文本占位符. 数值占位符 有三种:0,#,? 0 是强制的占位符。 文本占位符 文本占位符只有一个: : 作用于文本的占位符,可以用英文引号" &quo…

DirectX绘制流水线

使用DirectX可以让在Windows平台上运行的游戏或多媒体程序获得更高的执行效率,掌握DirectX的基本概念和技术是虚拟现实技术、计算机仿真和3D游戏程序开发的基础。 DirectX概述 DirectX是微软的一个多媒体应用编程接口(API)工具包,用于为Windows操作系统…

Qt QMovie和QLabel配合播放GIF表情包

文章目录 效果演示main函数创建MoviePlayer对象头文件movieplayer.h源文件movieplayer.cpp代码解释在Qt框架中,QMovie是用于处理动画和视频的类。所有源码已在本篇文章公布。 效果演示 main函数创建MoviePlayer对象 #include <QApplication>#include "movie

JAVAEE初阶相关内容第十三弹--文件操作 IO

写在前 终于完成了&#xff01;&#xff01;&#xff01;&#xff01;内容不多就是本人太拖拉&#xff01; 这里主要介绍文件input&#xff0c;output操作。File类&#xff0c;流对象&#xff08;分为字节流、字符流&#xff09; 需要掌握每个流对象的使用方式&#xff1a;打…

MySQL jdbc,事务,连接池

​​​ 3-MySQL jdbc,事务,连接 1 jdbc 1.1 jdbc概述# JDBC&#xff08;Java DataBase Connectivity,java数据库连接技术&#xff09;是一种用于执行SQL语句的Java API。 JDBC是Java访问数据库的标准规范&#xff0c;可以为不同的关系型数据库提供统一访问&#xff0c;它由一…

php对接微信支付简要流程?面试时你会描述吗?

一、微信支付申请&#xff1a;微信公众号平台-->功能中找到微信支付-->申请接入 1.如果没有微信支付商会号&#xff0c;需要进行申请 提交营业执照、身份证、银行账户 2.如果有微信支付商会号 可进行直接关联 登录微信商户平台—产品中心—APPID授权管理—新增授权申…

视频编辑软件 Premiere Pro 2024 macv24.0中文版 (pr2024)

Premiere Pro 2024 mac编辑任何现代格式的素材&#xff0c;从8K到虚拟现实。广泛的原生文件支持和简单的代理工作流程可以轻松使用您的媒体&#xff0c;即使在移动工作站上也是如此。提供针对任何屏幕或平台优化的内容比以往任何时候都快。 Premiere Pro 2024 Mac版软件介绍 视…

深度强化学习 第 2 章 蒙特卡洛

2.1随机变量 强化学习中会经常用到两个概念&#xff1a; 随机变量、 观测值。 本书用大写字母表示随机变量&#xff0c;小写字母表示观测值&#xff0c;避免造成混淆。 下面我们定义概率质量函数&#xff08;probability mass function&#xff0c;缩写 PMF&#xff09;和概率…

SpringBoot面试题5:SpringBoot Starter的工作原理是什么?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot Starter的工作原理是什么? Spring Boot Starter 是一种便捷的方式来为 Spring Boot 应用程序引入一组特定功能的依赖项。它简化了项目…

【Java并发】聊聊LongAdder应用场景及其原理

应用场景 我们知道在实际的应用场景中&#xff0c;可能会对某个商品进行浏览次数进行迭代&#xff0c;或者抖音视频的点击&#xff0c;那么如何高效记录呢&#xff0c;首先如果是使用普通的num 进行多线程操作的话&#xff0c;那么一定会带来数据一致性问题&#xff0c;所以一…

【Unity基础】6.动画状态机

【Unity基础】6.动画状态机 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;Animator Controller组件 &#xff08;1&#xff09;创建组件 Animator Controller组件是unity用于控制管…

【细读经典】delay model and timing analysis

Technology-Dependent LogicOptimization, part 1 序言 如图所示是现代工业流程中对于一个高层次的抽象描述如何到最后的芯片的流程图&#xff0c;其中逻辑综合作为一个非常重要的部分&#xff0c;主要被分为两个阶段&#xff1a; 工艺无关的优化(technology-independent opt…