通过示例详细了解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.mzph.cn/news/105861.shtml

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

相关文章

flask vue跨域问题

问题: 调试时候跨域访问报: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response. 解决办法: 安装flask_cros from flask_cors import CORS CORS(app) app.after_request def a…

如何避免大语言模型绕过知识库乱答的情况?LlamaIndex 原理与应用简介

本文首发于博客 LLM 应用开发实践 随着 LangChain LLM 方案快速普及,知识问答类应用的开发变得容易,但是面对回答准确度要求较高的场景,则暴露出一些局限性,比如向量查询方式得到的内容不匹配,LLM 对意图识别不准。所…

【Linux】多线程

文章目录 一.Linux线程概念1.什么是线程2.二级页表3.线程的优点4.线程的缺点5.线程异常6.线程用途 二.Linux进程VS线程1.进程和线程2.进程的多个线程共享3.进程和线程的关系 三.Linux线程控制1.POSIX线程库2.线程创建3.线程等待4.线程终止5.分离线程6.线程ID及进程地址空间布局…

手机拍摄的视频噪点很多怎么办,视频怎么做降噪处理?

现如今,智能手机已经成为了我们生活中必不可少的存在。而随着智能手机越来越强大,很多人已经开始使用手机来拍摄各种类型的视频。但是由于手机的限制,很多人会发现自己拍摄的视频存在着很多的噪点。那么,我们该怎样来解决拍摄视频…

N点复序列求2个N点实序列的快速傅里叶变换

一、方法简介 通过一个点复数序列求出两个点实数序列的离散傅里叶变换,进一步提升快速傅里叶变换的效率。 二、方法详解 和是实数序列,且长度都为,定义复数序列: , 则序列和可表示为: 的离散傅…

端到端的机器学习项目(Machine Learning 研习之六)

使用真实数据 当你在研习机器学习时,最好是使用真实世界中的数据,而不是采用人工数据。巧的是,数以千计的数据集可供选择,涵盖了各种领域。 流行的开放数据存储库: OpenML.orgKaggle.compaperswithcode.com UC Irvin…

MAYA教程之模型的UV拆分与材质介绍

什么是UV 模型制作完成后,需要给模型进行贴图,就需要用到UV功能 UV编译器介绍 打开UI编译器 主菜单有一个 UV->UV编译器,可以点击打开 创建一个模型,可以看到模型默认的UV UV编译器功能使用 UV模式的选择 在UV编译器中…

fastjson 1.2.47 远程命令执行漏洞

fastjson 1.2.47 远程命令执行漏洞 文章目录 fastjson 1.2.47 远程命令执行漏洞1 在线漏洞解读:2 环境搭建3 影响版本:4 漏洞复现4.1 访问页面4.2 bp抓包,修改参数 5 使用插件检测漏洞【FastjsonScan】5.1使用説明5.2 使用方法5.2.1 右键菜单中&#xff…

pycharm中快速对比两个.py文件

在学习一个算法的时候,就想着自己再敲一遍代码,结果最后出现了一个莫名其妙的错误,想跟源文件对比一下到底是在哪除了错,之前我都是大致定位一个一个对比,想起来matlab可以快速查找出两个脚本文件(.m文件)的区别&#…

Anylogic 读取和写入Excel文件

1、选择面板-连接-Excel文件,拖入到视图中 然后在excel文件的属性中进行绑定外部excel文件。 绑定完之后,在你需要读取的地方进行写代码, //定义开始读取的行数 //这里设为2,是因为第一行是数据名称 int row12; //读取excel文件信…

23面向对象案例1

目录 1、计算连续表达式的一个过程 2、优化后的代码 为什么不能return resultn? 3、用面向对象的方法可以解决冗余的问题,但是还是不能解决result的值可以被随意修改的问题 4、解决不能被随意修改的问题,可以将类属性改成私有变量吗&…

C++位图,布隆过滤器

本期我们来学习位图,布隆过滤器等相关知识,以及模拟实现,需求前置知识 C-哈希Hash-CSDN博客 C-封装unordered_KLZUQ的博客-CSDN博客 目录 位图 布隆过滤器 海量数据面试题 全部代码 位图 我们先来看一道面试题 给 40 亿个不重复的无符号…

STM32成熟变频逆变器方案

该方案是一款成熟的变频逆变器的方案,主要是把电源从直流到3相交流的转换,包含变频控制板,逆变主板,IO板,变频控制板主控是STM32F103VET6,配套软件。每一块板子都是原理图和PCB一一对应,并且配套…

基于springboot实现音乐网站与分享平台项目【项目源码+论文说明】计算机毕业设计

摘要 本论文主要论述了如何使用JAVA语言开发一个音乐网站与分享平台 ,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述音乐网站与分享平台的当前背景以及系统开…

Android Studio git 取消本地 commit(未Push)

操作比较简单 1.选中项目然后依次选择:Git->Repository->Reset HEAD 2.然后再to Commit中输入HEAD^,表示退回到上一个版本。

Js高级技巧—拖放

拖放基本功能实现 拖放是一种非常流行的用户界面模式。它的概念很简单:点击某个对象,并按住鼠标按钮不放,将 鼠标移动到另一个区域,然后释放鼠标按钮将对象“放”在这里。拖放功能也流行到了 Web 上,成为 了一些更传统…

【树莓派 picamera】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言https://www.cnblogs.com/uestc-mm/p/7606855.html 一、picamera是什么?二、使用步骤1.引入库2.先要安装opencv 总结 前言 想用树莓派libcamera &a…

AI低代码维格云日历视图怎么用?

日历视图,是一个以天为单位,清晰展示当月所有日程的视图。在团队协作的过程中,我们常常会碰到以下场景: 制作项目日历,让团队成员知道每天需要完成什么任务; 制作排课表,给老师和教室安排课程; 制作会议日历,提醒团队成员进行每周计划与回顾; 制作营销日历,把握全年…

新时代高效记账:自动化智能如何进行财务管理

随着科技的不断发展,自动化智能已经逐渐渗透到我们生活的各个领域。在财务管理中,自动化智能的应用显得尤为重要。它不仅可以提高财务管理的效率和精度,还能帮助我们更好地规划和掌控公司的财务状况 晨曦记账本提供了多种高效财务管理工具。…

单元测试到底是什么?应该怎么做?

我是Java程序员出身,后来因为工作原因转到到了测试开发岗位。测试开发工作很多年后。针对标题的两个问题,我还有些发言权,特来说下: 1、什么是单元测试 2、该怎么做单元测试 一、什么是单元测试? 单元测试&#xff08…