专栏首页京程一灯当一个模块被导入两次时,会发生什么?

当一个模块被导入两次时,会发生什么?

翻译:疯狂的技术宅

作者:Dmitri Pavlutin

来源:dmitripavlutin

正文共:2497 字

预计阅读时间:7 分钟

让我们从一个问题开始。

名为 increment 的 ES2015 模块包含以下代码:

// increment.js
let counter = 0;
counter++;

export default counter;

然后在另一个模块 consumer 中,将上述模块 increment 导入两次:

// consumer.js
import counter1 from './increment';
import counter2 from './increment';

counter1; // => ???
counter2; // => ???

问题是:当 consumer 模块运行时,变量 counter1counter2 的内容是什么?

要回答这个问题,首先你需要了解 JavaScript 如何评估和导入模块。

1. 模块评估

理解 JavaScript 内部原理的一个好方法是查看其说明。

根据规范,每个 JavaScript 模块都与模块记录相关联。模块记录具有方法 Evaluate(),该方法对模块进行评估:


如果该模块已经被成功评估,则返回 undefined;……否则,便可递归地评估此模块所有的模块依赖性,然后再评估此模块。


所以同一模块仅被评估一次。

不幸的是,问题不止于此。如何确保使用相同路径两次调用 import 语句返回相同的模块?

2. 解决导入问题

将路径(也称为说明符)关联到具体模块的职责由 HostResolveImportedModule() 执行操作。

import module from 'path';

根据规则:


HostResolveImportedModule 的实现必须符合以下要求:

  • 正常的返回值必须是 Module Record 的具体子类的实例。
  • 如果与 referencingScriptOrModule, specifier对相对应的 Module Record 不存在或无法创建,则必须引发异常。
  • 每次使用特定的 referencingScriptOrModule, specifier 对作为参数调用此操作时,如果正常完成,则必须返回相同的 Module Record 实例。

让我们以一种易于理解的方式看看都发生了什么。

HostResolveImportedModule(referencingScriptOrModule, specifier) 是一个抽象操作,该操作返回对应于ReferencingScriptOrModule, specifier 的模块:

  • 参数 referencingScriptOrModule 是当前模块,即进行导入的模块。
  • 参数 specifier 是对应 import 语句中模块路径的字符串。

最后,HostResolveImportedModule() 从相同路径导入模块时,将导入相同模块:

import moduleA from 'path';
import moduleB from 'path';
import moduleC from 'path';

// moduleA, moduleB and moduleC are equal

moduleA === moduleB; // => true
moduleB === moduleC; // => true

有趣的是,规范指出主机(指浏览器,Node 或任何尝试运行 JavaScript 的东西)必须提供 HostResolveImportedModule() 的具体实现。

3. 答案

查看规范之后,你将知道对 JavaScript 模块进行了一次评估。另外,从相同路径导入模块时,将返回相同的模块实例。

让我们回到问题。

increment 模块总是被评估一次:

// increment.js
let counter = 0;
counter++;

export default counter;

不管 increment 模块被导入多少次,counter++ 语句仅执行一次。默认导出的 counter 变量的值为 1

现在看 consumer 模块:

// consumer.js
import counter1 from './increment';
import counter2 from './increment';

counter1; // => 1
counter2; // => 1

import counter1 from './increment'import counter2 from './increment' 有相同的路径:'./increment'。所以你将会导入相同的模块实例。

最后,consumer 内部的 counter1counter2 变量都等于 1

4. 结论

仅通过研究提出的简单问题,就可以找到有关如何评估和导入 JavaScript 模块的详细信息。

规则非常简单:同一模块仅被评估一次,换句话说,模块级作用于仅被执行一次。如果评估后的模块再次导入,则会跳过第二次评估,并使用已解决的已导出文件。

如果某个模块多次导入但使用相同的说明符(即路径),则 JavaScript 规范可确保你将得到相同的模块实例。

原文链接

https://dmitripavlutin.com/javascript-module-import-twice/

本文分享自微信公众号 - 前端先锋(jingchengyideng),作者:疯狂的技术宅

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何管理好10万行代码的前端单页面应用

    蚂蚁金服数据平台前端团队主要负责多个数据相关的PC Web单页面应用程序,业务复杂度类比Excel等桌面应用,业务前端代码量在几万行~几十万行,随着产品不断完善...

    疯狂的技术宅
  • Node.js 中的ES模块现状[每日前端夜话0x8D]

    新的 ECMAScript(ES)模块与以前的语言版本不完全兼容,因此使用的 JavaScript 引擎需要知道每一个文件是“旧” JavaScript 代码还...

    疯狂的技术宅
  • 怎样避免Node.js模块的日志污染程序日志

    你是否有过这样的经历,当把 logging 添加到自定义 Node 模块中,并认为自己将会从这些额外信息中受益,却发现当你将模块添加为依赖项并运行 npm in...

    疯狂的技术宅
  • Python学习笔记整理(十三)Pyth

    一、模块 模块是Pyhon最高级别的程序组织单元,它将程序代码和数据封装起来以便重用。实际的角度,模块往往对应Python程序文件。 每个文件都是一个模块,...

    py3study
  • Python模块

    Python模块 可以将代码量较大的程序分割成多个有组织的、彼此独立但又能相互交互的代码片段,这些自我包含的有组织的代码段就是模块 模块在物理形式上表现为以.p...

    企鹅号小编
  • python 对象 特殊字段

    https://www.cnblogs.com/zh1164/p/6031464.html

    小贝壳
  • Thinking--IOC思想在前端中的应用

    系统中,经常会出现 A 模块,依赖 B 模块,同时也依赖 C 模块的情况。我们通常的处理方式是将 B、C 模块直接引入到 A 模块中,这个获取过程都在 A 模块...

    奋飛
  • 【npm】详解npm的模块安装机制

    依赖树表面的逻辑结构与依赖树真实的物理结构 依赖树表面的逻辑结构与依赖树真实的物理结构并不一定相同! 这里要先提到两个命令:tree -d(linux)和npm...

    外婆的彭湖湾
  • 前端模块化开发解决方案详解

    AMD(常用在浏览器端,异步的,如requirejs)(Asynchronous Module Definition)

    半指温柔乐
  • 基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET - 模块插件集成

          前面我们在AgileEAS.NET之插件接口IModule和AgileEAS.NET之插件运行容器中对模块插件和运行容器都做了介绍,本文我们介绍Ag...

    魏琼东

扫码关注云+社区

领取腾讯云代金券