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

通过示例详细了解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.mjsexport { foo };export default function foo() {};setTimeout(() => {foo = 'baz';}, 1000);
  // index.mjsimport defaultFoo, { foo } from './module.mjs';setTimeout(() => {console.log(foo); // bazconsole.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 functionmodule.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.rhkb.cn/news/159506.html

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

相关文章

深度学习-卷积神经网络

文章目录 应用卷积神经网络卷积处理分类问题 应用 图片分类图片检索图片分割图片风格迁移姿态估计OCR等 卷积神经网络 核概念计算机视觉中处理图片的核大小是通过经验得来的,而深度学习中的权重大小是自己学习出的。卷积VS神经网络:一个是局部观察一个…

【实操】基于ChatGPT构建知识库

前言 最近有些实践,因为后面要去研究fine-tune了,想着记录一下chatgpt向量数据库构建知识库的一些实操经验,不记我很快就忘了,哈哈。 首先,提一下为啥会出现向量数据库这个技术方案? 大家经过实践发现&…

【传输层协议】UDP/TCP结构特点与原理(详解)

文章目录 1. UDP1.1 UDP结构1.2 UDP特点1. 无连接2. 不可靠3. 面向数据报4. 缓冲区5. 大小受限6. 无序性 2. TCP2.1 TCP结构2.2 TCP特点1. 有连接2. 可靠性3. 面向字节流4. 拥塞控制5. 头部开销 2.3 TCP原理1. 确认应答(安全机制)2. 超时重传&#xff08…

数据结构与算法--其他算法

数据结构与算法--其他算法 1 汉诺塔问题 2 字符串的全部子序列 3 字符串的全排列 4 纸牌问题 5 逆序栈问题 6 数字和字符串转换问题 7 背包问题 8 N皇后问题 暴力递归就是尝试 1,把问题转化为规模缩小了的同类问题的子问题 2,有明确的不需要继续…

使用Elasticsearch来进行简单的DDL搜索数据

说明:Elasticsearch提供了多种多样的搜索方式来满足不同使用场景的需求,我们可以使用Elasticsearch来进行各种复制的查询,进行数据的检索。 1.1 精准查询 用来查询索引中某个类型为keyword的文本字段,类似于SQL的“”查询。 创…

【ElasticSearch】使用 Java 客户端 RestClient 实现对文档的查询操作,以及对搜索结果的排序、分页、高亮处理

文章目录 前言:RestClient 查询文档的 RestAPI一、全文检索查询1.1 match_all 查询1.2 match 查询1.3 multi_match 查询 二、精确查询2.1 term 查询2.2 range 查询 三、复合查询:Boolean 查询与 function score 查询的综合案例四、对查询结果的处理4.1 将…

050:mapboxGL加载geojson数据,同时包含点、多边形的处理示例

第050个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载geojson数据,既显示点又显示多边形。这个示例是显示了一种处理方式,通过过滤的方式将数据分离化,点和多边形通过两个不同的图层来加载表示。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实…

2018-2019 ACM-ICPC, Asia Nanjing Regional Contest G. Pyramid(组合数学 计数)

题目 t(t<1e6)组样例&#xff0c;每次给定一个n(n<1e9)&#xff0c;统计边长为n的上述三角形的等边三角形个数 其中等边三角形的三个顶点&#xff0c;可以在所有黑色三角形&白色三角形的顶点中任取&#xff0c; 答案对1e97取模 思路来源 申老师 & oeis A0003…

本地生活将成快手新的营收增长点

监制 | 何玺 排版 | 叶媛 快手本地生活开始强化B端市场。 据了解&#xff0c;快手 “本地商家”APP已经正式上线。这是快手为本地生活商家推出的独立工作平台&#xff0c;有助于商家提升经营效率。 新APP的上线&#xff0c;标志着快手本地生活业务布局&#xff0c;正从过去侧…

深入理解Kafka分区副本机制

1. Kafka集群 Kafka 使用 Zookeeper 来维护集群成员 (brokers) 的信息。每个 broker 都有一个唯一标识 broker.id&#xff0c;用于标识自己在集群中的身份&#xff0c;可以在配置文件 server.properties 中进行配置&#xff0c;或者由程序自动生成。下面是 Kafka brokers 集群自…

TLS/SSL 详解

目录 基础理论入门HTTPS对称加密非对称加密证书TLS握手过程握手总结 TLS 定义(记录层/握手层)HTTPS HTTP over TLS加密记录层分片 (Fragmentation)记录压缩和解压缩 (Record compression and decompression)空或标准流加密 (Null or standard stream cipher)CBC 块加密 (分组加…

VS2022新建项目时没有ASP.NET Web应用程序 (.NET Framework)

问题&#xff1a;如图&#xff0c;VS2022新建项目时没有“ASP.NET Web应用程序 &#xff08;.NET Framework&#xff09;”的选项解决方法&#xff1a;点击跳转至修改安装选项界面选择安装该项即可&#xff1a;

k8s-13 存储之secret

Secret 对象类型用来保存敏感信息&#xff0c;例如密码、OAuth 令牌和 ssh key。 敏感信息放在 secret 中比放在 Pod 的定义或者容器镜像中来说更加安全和灵活 。 Pod 可以用两种方式使用 secret:作为 volume 中的文件被挂载到 pod 中的一个或者多个容器里 当 kubelet 为 pod 拉…

python:从Excel或者CSV中读取因变量与多个自变量,用于训练机器学习回归模型,并输出预测结果

作者:CSDN @ _养乐多_ 本文详细记录了从Excel读取用于训练机器学习模型的数据,包括独立变量和因变量数据,以供用于机器学习模型的训练。这些机器学习模型包括但不限于随机森林回归模型(RF)和支持向量机回归模型(SVM)。随后,我们将测试数据集应用于这些模型,进行预测和…

[开源]基于Vue+ElementUI+G2Plot+Echarts的仪表盘设计器

一、开源项目简介 基于SpringBoot、MyBatisPlus、ElementUI、G2Plot、Echarts等技术栈的仪表盘设计器&#xff0c;具备仪表盘目录管理、仪表盘设计、仪表盘预览能力&#xff0c;支持MySQL、Oracle、PostgreSQL、MSSQL、JSON等数据集接入&#xff0c;对于复杂数据处理还可以使用…

彩虹易支付 9.27 最新版加订单查询 sy 更新版

彩虹易支付 9.27 最新版加订单查询 sy 更新版 修复客服 2023/09/25&#xff1a; 1. 新增支付宝红包支付插件 2. 新增支付宝 APP 支付转 H5 支付 3. 更新了几个支付插件 安装教程&#xff1a; 环境&#xff1a;php7.2 上传后访问域名进行安装即可 源码下载&#xff1a;ht…

KdMapper扩展实现之SOKNO S.R.L(speedfan.sys)

1.背景 KdMapper是一个利用intel的驱动漏洞可以无痕的加载未经签名的驱动&#xff0c;本文是利用其它漏洞&#xff08;参考《【转载】利用签名驱动漏洞加载未签名驱动》&#xff09;做相应的修改以实现类似功能。需要大家对KdMapper的代码有一定了解。 2.驱动信息 驱动名称spee…

POI报表的高级应用

POI报表的高级应用 掌握基于模板打印的POI报表导出理解自定义工具类的执行流程 熟练使用SXSSFWorkbook完成百万数据报表打印理解基于事件驱动的POI报表导入 模板打印 概述 自定义生成Excel报表文件还是有很多不尽如意的地方&#xff0c;特别是针对复杂报表头&#xff0c;单…

macbook电脑删除app怎么才能彻底清理?

macBook是苹果公司推出的一款笔记本电脑&#xff0c;它的操作系统是macOS。在macBook上安装的app可能会占用大量的存储空间&#xff0c;因此&#xff0c;当我们不再需要某个app时&#xff0c;需要将其彻底删除。macbook删除app&#xff0c;怎么才能彻底呢&#xff1f;本文将给大…

c#设计模式-行为型模式 之 备忘录模式

&#x1f680;简介 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;它保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象。所谓备忘录模式就是在不破坏封装的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象…