深入 ES6:模块

深入 ES6 是一个系列文章,介绍了 ECMAScript 标准第 6 版(简称 ES6)中添加的 JavaScript 编程语言的新特性。

当我于 2007 年加入 Mozilla 的 JavaScript 团队时,大家都在开玩笑说,一个典型的 JavaScript 程序只有一行代码。

那是 Google 地图发布两年后。在那之前不久,JavaScript 的主要用途是表单验证,事实证明,你的平均 <input onchange=> 处理程序也只有一行代码。

情况已经改变。JavaScript 项目已经发展到令人瞠目结舌的规模,并且社区已经开发了用于大规模工作的工具。你需要做的一件最基本的事情是使用一个模块系统,它可以将你的工作分散到多个文件和目录中,但同时也要确保你所有的代码片段都能根据需要相互访问,并且能够高效地加载所有这些代码。因此,JavaScript 自然而然地具有一个模块系统,甚至有几个。还有几个包管理器,这些工具可以用来安装所有这些软件并处理高级依赖关系。你可能认为 ES6 的新模块语法有点晚。

今天,我们将看看 ES6 是否为这些现有系统添加了任何内容,以及未来的标准和工具是否能够在它之上构建。但首先,让我们直接深入了解一下 ES6 模块的外观。

模块基础

ES6 模块是一个包含 JS 代码的文件。没有特殊的 module 关键字;模块看起来就像一个脚本。有两个区别。

  • ES6 模块是自动严格模式代码,即使你没有在其中写入 "use strict";

  • 你可以在模块中使用 importexport

让我们先谈谈 export。默认情况下,模块内部声明的所有内容对模块都是局部的。如果你希望模块中声明的内容是公开的,以便其他模块可以使用它,你必须导出该功能。有几种方法可以做到这一点。最简单的方法是添加 export 关键字。

<pre>
// kittydar.js - 在图像中查找所有猫的位置。
// (<a href="https://harthur.github.io/kittydar/" target="_blank">Heather Arthur 编写了这个真实库</a>)
// (但她没有使用模块,因为它是在 2013 年)

<strong>export</strong> function detectCats(canvas, options) {
var kittydar = new Kittydar(options);
return kittydar.detectCats(canvas);
}

<strong>export</strong> class Kittydar {
... 做图像处理的几种方法 ...
}

// 此辅助函数未导出。
function resizeCanvas() {
...
}
...
</pre>

你可以 export 任何顶层 functionclassvarletconst

实际上,这就是你需要了解的所有内容来编写模块!你无需将所有内容都放入 IIFE 或回调中。只需声明所有你需要的东西即可。因为代码是一个模块,而不是一个脚本,所以所有声明都将作用域到该模块,而不是全局可见于所有脚本和模块。导出构成模块的公开 API 的声明,你就完成了。

除了导出之外,模块中的代码基本上就是正常的代码。它可以使用像 ObjectArray 这样的全局变量。如果你的模块在 Web 浏览器中运行,它可以使用 documentXMLHttpRequest

在另一个文件中,我们可以导入并使用 detectCats() 函数

<pre>
// demo.js - Kittydar 演示程序

import {detectCats} from "kittydar.js";

function go() {
var canvas = document.getElementById("catpix");
var cats = detectCats(canvas);
drawRectangles(canvas, cats);
}
</pre>

要从一个模块中导入多个名称,你可以这样写

<pre>
import {detectCats, Kittydar} from "kittydar.js";
</pre>

当运行包含 import 声明的模块时,首先会加载它导入的模块,然后以深度优先遍历依赖关系图的方式执行每个模块主体,通过跳过已执行的内容来避免循环。

这就是模块的基础知识。真的很简单。 ;-)

导出列表

与其为每个导出的功能添加标签,你可以写出一个包含所有你想要导出的名称的单一列表,并将其括在花括号中

<pre>
export {detectCats, Kittydar};

// 此处不需要 `export` 关键字
function detectCats(canvas, options) { ... }
class Kittydar { ... }
</pre>

export 列表不必是文件中第一项;它可以出现在模块文件顶层作用域的任何地方。你可以在多个 export 列表中混合使用,或将 export 列表与其他 export 声明混合使用,只要没有名称被导出多次即可。

重命名导入和导出

有时,导入的名称恰好与你需要使用的其他名称发生冲突。因此,ES6 允许你在导入时重命名它们

<pre>
// suburbia.js

// 这两个模块都导出了一个名为 `flip` 的东西。
// 要导入它们,我们必须至少重命名其中一个。
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";
...
</pre>

类似地,你可以在导出时重命名它们。如果你希望以两个不同的名称导出相同的值,这会很方便,这种情况偶尔会发生

<pre>
// unlicensed_nuclear_accelerator.js - 无 DRM 的媒体流
// (不是一个真正的库,但也许应该有一个)

function v1() { ... }
function v2() { ... }

export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
</pre>

默认导出

新的标准旨在与现有的 CommonJS 和 AMD 模块互操作。所以,假设你有一个 Node 项目,你已经执行了 npm install lodash。你的 ES6 代码可以从 Lodash 导入单个函数

<pre>
import {each, map} from "lodash";

each([3, 2, 1], x => console.log(x));
</pre>

但也许你已经习惯了看到 _.each 而不是 each,你仍然想要以这种方式编写代码。或者也许你想将 _ 作为函数使用,因为 在 Lodash 中这样做很有用

为此,你可以使用稍微不同的语法:在没有花括号的情况下导入模块。

<pre>
import _ from "lodash";
</pre>

此简写等效于 import {default as _} from "lodash";。所有 CommonJS 和 AMD 模块都被呈现给 ES6,它们具有一个 default 导出,它与你要求 require() 获取该模块时得到的结果相同,也就是说,exports 对象。

ES6 模块旨在让你导出多个内容,但对于现有的 CommonJS 模块而言,default 导出是你唯一可以获得的东西。例如,在撰写本文时,著名的 colors 包据我所知还没有任何特殊的 ES6 支持。它是一组 CommonJS 模块,就像 npm 上的大多数包一样。但你可以在你的 ES6 代码中直接导入它。

<pre>
// ES6 等效于 `var colors = require("colors/safe");`
import colors from "colors/safe";
</pre>

如果你希望你自己的 ES6 模块具有 default 导出,这很容易做到。default 导出并没有什么神奇之处;它与其他任何导出都一样,只是它被命名为 "default"。你可以使用我们之前讨论过的重命名语法

<pre>
let myObject = {
field1: value1,
field2: value2
};
export {myObject as default};
</pre>

或者更好的是,使用此简写

<pre>
<strong>export default</strong> {
field1: value1,
field2: value2
};
</pre>

关键字 export default 后面可以跟任何值:函数、类、对象字面量,任你所选。

模块对象

抱歉,这篇文章太长了。但 JavaScript 并非孤例:出于某种原因,所有语言的模块系统都倾向于拥有大量单独的小而无聊的便利功能。幸运的是,只剩下一个了。好吧,两个。

<pre>
import * as cows from "cows";
</pre>

当你 import * 时,导入的是一个 模块命名空间对象。它的属性是模块的导出。因此,如果“cows”模块导出了一个名为 moo() 的函数,那么以这种方式导入“cows”后,你可以这样写:cows.moo()

聚合模块

有时,包的主模块仅仅是导入包的其他所有模块并将它们以统一的方式导出。为了简化这种代码,有一个一键式导入和导出简写

<pre>
// world-foods.js - 来自世界各地的美味佳肴

// 导入 "sri-lanka" 并重新导出其部分导出内容
export {Tea, Cinnamon} from "sri-lanka";

// 导入 "equatorial-guinea" 并重新导出其部分导出内容
export {Coffee, Cocoa} from "equatorial-guinea";

// 导入 "singapore" 并导出其所有导出内容
export * from "singapore";
</pre>

每个 export-from 语句类似于一个 import-from 语句,后面跟着一个 export。与真实的导入不同,这不会将重新导出的绑定添加到你的作用域中。所以,如果你打算在 world-foods.js 中编写一些使用 Tea 的代码,请不要使用此简写。你会发现它不在那里。

如果“singapore”导出的任何名称恰好与其他导出内容发生冲突,那将是一个错误,因此请谨慎使用 export *

哇!我们完成了语法!接下来是更有趣的部分。

import 到底做了什么?

你能相信吗?……什么都没做!

哦,你没那么容易上当。好吧,你能相信标准基本上没有说明 import 做了什么吗?这其实是一件好事吗?

ES6 将模块加载的细节完全留给实现决定。模块执行的其余部分在详细说明

简单来说,当你告诉 JS 引擎运行一个模块时,它必须表现得像在执行以下四个步骤一样。

  1. 解析:实现会读取模块的源代码并检查语法错误。

  2. 加载:实现会加载所有导入的模块(递归地)。这是目前尚未标准化的一部分。

  3. 链接:对于每个新加载的模块,实现都会创建一个模块作用域,并用该模块中声明的所有绑定填充它,包括从其他模块导入的内容。

    如果尝试运行 `import {cake} from "paleo"`,但“paleo”模块实际上没有导出名为 `cake` 的任何内容,就会出现错误。这太可惜了,因为你已经非常接近实际运行一些 JS 代码了。而且还有蛋糕!

  4. 运行时:最后,实现会运行每个新加载模块主体中的语句。到此时,`import` 处理已经完成,所以当执行到达代码行,并且该行包含 `import` 声明时……什么都不会发生!

看到了吧?我告诉你答案是“什么都不会发生”。我不会说谎关于编程语言。

但是现在我们来到了这个系统有趣的部分。有一个很酷的技巧。因为系统没有指定加载是如何工作的,而且你可以通过查看源代码中的 `import` 声明提前找出所有依赖关系,所以 ES6 的实现可以自由地在编译时完成所有工作,并将所有模块捆绑到一个文件中以便通过网络传输!像 webpack 这样的工具实际上就是这样做的。

这是一件大事,因为通过网络加载脚本需要时间,而且每次你获取一个脚本时,你可能会发现它包含需要你加载数十个其他脚本的 `import` 声明。一个简单的加载器需要很多网络往返。但是使用 webpack,你不仅可以在今天使用带有模块的 ES6,而且还可以在没有任何运行时性能损失的情况下获得所有软件工程优势。

ES6 中模块加载的详细规范最初是计划的——并且已经构建。它没有出现在最终标准中的原因之一是,关于如何实现这种捆绑功能没有达成共识。我希望有人能解决这个问题,因为正如我们所见,模块加载确实应该标准化。捆绑太棒了,不能放弃。

静态与动态,或者:规则和如何打破它们

对于一门动态语言来说,JavaScript 却为自己创造了一个出奇静态的模块系统。

  • 所有类型的 `import` 和 `export` 只能在模块的顶层使用。没有条件导入或导出,你不能在函数作用域中使用 `import`。

  • 所有导出的标识符都必须在源代码中显式地按名称导出。你不能以数据驱动的方式编程地循环遍历一个数组并导出大量名称。

  • 模块对象是冻结的。没有办法以类似于 polyfill 的方式将新功能添加到模块对象中。

  • 模块的所有依赖关系都必须在任何模块代码运行之前被 eagerly 加载、解析和链接。没有语法用于按需延迟加载的 `import`。

  • 对于 `import` 错误没有错误恢复机制。一个应用程序可能包含数百个模块,如果任何模块加载或链接失败,就什么也无法运行。你不能在 `try/catch` 块中使用 `import`。(这里的好处是,由于系统非常静态,webpack 可以帮助你在编译时检测这些错误。)

  • 没有允许模块在依赖关系加载之前运行一些代码的钩子。这意味着模块无法控制其依赖关系的加载方式。

只要你的需求是静态的,这个系统就相当不错。但是你可以想象有时需要一些小 hack,对吧?

这就是为什么无论你使用哪种模块加载系统,它都将有一个编程 API 与 ES6 静态 `import/export` 语法一起使用。例如,webpack 包含一个 API,你可以用它来进行“代码分割”,按需延迟加载一些模块捆绑包。同一个 API 可以帮助你打破上面列出的其他大多数规则。

ES6 模块 *语法* 非常静态,这很好——它以强大编译时工具的形式得到回报。但是静态语法被设计为与一个丰富的动态编程加载器 API 一起工作。

我什么时候可以使用 ES6 模块?

若要使用今天的模块,你需要一个编译器,例如 TraceurBabel。在本系列的早期,Gastón I. Silva 展示了如何使用 Babel 和 Broccoli 为 Web 编译 ES6 代码;在本文的基础上,Gastón 提供了一个支持 ES6 模块的示例。这篇 由 Axel Rauschmayer 撰写的文章 包含一个使用 Babel 和 webpack 的示例。

ES6 模块系统主要由 Dave Herman 和 Sam Tobin-Hochstadt 设计,他们在多年的争议中,针对所有反对者(包括我)捍卫了该系统静态部分。Jon Coppeard 正在 Firefox 中实现模块。关于 JavaScript 加载器标准 的额外工作正在进行中。在 HTML 中添加类似 `<script type=module>` 的内容的工作预计会紧随其后。

这就是 ES6。

这段旅程太有趣了,我不想让它结束。也许我们应该再做一期。我们可以谈谈 ES6 规范中一些不够重要而没有单独文章的零散内容。也许还可以谈谈未来会发生什么。请下周加入我,欣赏 ES6 深入解析的精彩结局。

关于 Jason Orendorff

更多 Jason Orendorff 的文章…


33条评论

  1. Filipe Silva

    这是一系列非常棒的文章。感谢您撰写它们。

    我一直关注着每一期,现在我正在更新公司前端的一部分内容,以便使用 es6,阅读这些见解真的很有帮助。

    2015 年 8 月 15 日 上午 2:06

  2. Vincent

    感谢您撰写这系列文章!期待着结局 :)

    2015 年 8 月 15 日 上午 2:20

  3. voracity

    因此,“类”将我们带入了静态的领域,但实际上它只是将每个人以前用 `new` 混乱地做的事情编码起来。(每个人都已经被充分警告不要过度使用“扩展”,因此希望不会出现层次结构噩梦。)

    这看起来很不同。我几年以前第一次看到它的时候并没有多想,但如果“类”关键字有点反 JS(过去我一直将 JS 作为“几乎所有东西都是动态的”的同义词),那么这看起来完全是反 JS。

    我们迫切需要的是一个简单的内置 `import()` 函数,其工作方式与 `require()` 类似,也许还可以与装饰器(用于导出或私有化事物)和解构相结合,以选择我们想要导入的内容。(当然,我们已经有了用于这种事情的解构,但是由于某种原因,模块解构的语法与标准 ES6 解构略有不同!为什么呢?)总之,整个过程可以解构为非常简单的 ES6 减去模块。不需要 DSL。(我最近没有在这里表达过对此的担忧吗?:\ ) 我们现在得到的是很多特定于模块的语法,它编码了一些人关于什么最适合 Web 的 *静态* 猜测(从多个方面来说都是静态的),这些猜测可能在 10 年左右的时间内就变得无关紧要。

    抱歉,这听起来有点消极。在更积极的方面:(大部分)我非常喜欢 ES6 的文章,看到它们结束让我很失望。

    2015 年 8 月 15 日 上午 5:52

    1. Jason Orendorff

      我希望我在写这篇文章的时候能看到你的回应。那将是一篇更好的文章。

      一个“简单”的同步 `require()` 从未出现在 ES 中,因为 ES 必须在浏览器中工作。加载模块可能涉及从互联网上获取代码,这无法同步完成。某种异步操作,例如 require.js 提供的 `require()` 函数,将会被标准化。但是那种 API 在你编写的每个文件中使用起来都不美观。Node 用户不会迁移到一个异步 API;他们会坚持使用 `require()`。新的 `import` 语法是一种方法——也许是唯一的方法——来提供一个客户端和服务器端 JS 代码都会实际使用的标准模块系统。

      称设计中的静态部分为“猜测”是不公平的,因为这忽略了模块化编程语言几十年的历史。看看 10 年和 20 年前设计的系统,你会发现 ES6 中的内容是程序员需要的,很难想象在 10 年后这些基本需求会发生根本变化。我们拭目以待。我认为设计师受到 Racket 和可能还有 Python(两种具有良好模块系统的动态语言)的影响。他们还从 JS 社区的经验中获益,特别是 Yehuda Katz。

      我真的很喜欢这个系统。它在实践中非常好用,是之前所做工作的重大改进。

      我还迫切希望动态 API 被标准化。我会在这方面稍微敦促一下标准委员会。在写这篇文章的时候,我意识到有可能在短期内标准化其中一些 API,而加载过程的细节(特别是用户配置和加载定制)仍在制定中。

      2015 年 8 月 15 日 上午 8:42

      1. voracity

        “新的导入语法是一种方法——也许是唯一的方法——来提供一个客户端和服务器端 JS 代码都会实际使用的标准模块系统。”啊,明白了。因此使用了一种新的语法,以便导入的语法在同步和异步加载之间是无关的?

        但是它看起来并不特别无关。正如你下面提到的,包含这种导入语法的 JS 块必须阻塞(以及它之后的所有内容),直到导入完成。因此,正如 PhistucK 所提到的,不可能像现在这样在客户端使用这种语法。事实上,这种语法显然不是异步友好的。

        无论如何,根据 David Mulder 的说法,yield 和 async/await 将是服务器端的绝佳解决方案,不是吗?我已经在 node.js 中使用 yield 来简化数据库代码。事实上,我开始认为每个 JS 代码块都应该隐式地被视为异步的。(也许这太昂贵了?)

        抱歉,“直觉”这个词有点沉重。它是针对 web 应用模块(特别是基于网络的模块加载)这个相对较新的领域,这将要求*任何人*对所需内容进行猜测,而不是针对传统软件中的模块。其中一个猜测是异步故事将如何发展。反思一下,我认为这可能是为什么 HTML 导入/web 组件/覆盖层或任何类似的东西难以实现的原因——也就是说,因为没有人真正清楚我们到底需要/想要什么。

        我想语法会很有用,但它在概念上与 JS 中的任何其他东西都没有任何联系。:) 但我迫不及待地想要看到动态 API。System.import(如果我们得到它)看起来非常不错。

        2015 年 8 月 16 日 下午 6:21

        1. Jason Orendorff

          > 所以正如 PhistucK 所提到的,无法在客户端使用这种语法(就目前而言)。

          人们已经通过编译在客户端使用它了。你应该试一试;试驾可能比在这里进一步讨论更有启发。

          未来的标准将使其无需编译即可实现。

          > 事实上,这种语法显然不适合异步。

          我不同意。在像浏览器这样的环境中,模块系统的工作方式与 require.js 非常相似,所有 I/O 都是异步的。这正是 CommonJS 的require() 函数无法做到的事情,因为它是一个同步函数。

          2015 年 8 月 17 日 下午 1:42

  4. PhistucK

    如果我混合模块和全局代码会怎样?
    模块是否可以访问开发者定义的全局变量和对象?

    例如——

    // module.js
    export function bar()
    {
    return foo;
    }
    // 常规外部脚本
    import * from “module.js”
    var foo = 8;

    2015 年 8 月 15 日 上午 7:32

    1. PhistucK

      更准确地说——
      // 常规外部脚本
      import { bar } from “module.js”
      var foo = 8;
      console.log(bar());

      它应该打印 8 吗?

      2015 年 8 月 15 日 上午 7:33

    2. Jason Orendorff

      模块确实可以访问全局变量,是的。就像函数的范围一样,它们嵌套在全局范围内。

      但是普通的<script> 脚本不能使用import 语法。其中一个原因是,普通的<script> 会同步运行并阻塞网页。如果你仔细想想,使用import,每个<script> 可能不仅加载单个文件,还会加载整个依赖树。这绝对不是你想要的。

      这留下了一个问题:模块可以轻松访问全局变量,但全局脚本如何访问模块?一种方法是让脚本使用由你的模块加载器提供的API。该 API 与 require.js 类似,因此这并不难或令人不愉快。只是不像真正的import 语法那样好。

      从长远来看,你将使用模块来完成所有事情,因此你将在所有地方使用import

      2015 年 8 月 15 日 上午 9:14

      1. PhistucK

        嗯...虽然你回答了我的问题,但你让事情变得更难以理解了...
        那么,模块的入口点是什么?如果你不能在(script src=”stuff.js”)中使用它,其中 stuff.js 有 “import { bar } from ‘baz.js'”?
        它从哪里开始?模块加载器(例如 require.js)仍然使用 XMLHttpRequest(然后是 eval,它不能用于模块,对吧?)或脚本元素(你说也不能用于模块)加载东西。

        2015 年 8 月 15 日 下午 12:23

        1. Jason Orendorff

          抱歉!

          模块的入口点*目前*是所有模块都编译成 ES5 脚本。然后就简单了。你只需像加载任何其他脚本一样加载它们,使用<script src=...>

          一旦浏览器获得对模块的原生支持,入口点将类似于<script type="module" src=...>

          2015 年 8 月 17 日 上午 1:02

          1. PhistucK

            嗯,我不明白。假设浏览器实现了 ECMAScript 2015 版本的模块。我将如何使用模块?
            script type=”module” 是否已标准化?如果不是,那这不是一个方法。假设浏览器实现了整个 ECMAScript 2015,在浏览器中使用模块的标准方法是什么?

            2015 年 8 月 17 日 上午 1:22

          2. Jason Orendorff

            > script type=”module” 是否已标准化?

            还没有。

            > 假设浏览器实现了整个 ECMAScript 2015,在浏览器中使用模块的标准方法是什么?

            还没有。

            ES6 只指定了 JS 语言部分,而不是 HTML 部分。这些部分将在以后出现。在此期间,你可以使用编译。

            2015 年 8 月 17 日 下午 1:51

  5. David Mulder

    之前已经玩过模块,但阅读这篇文章后,我意识到,即使 ES6 普遍可用,我可能仍然会坚持使用 `require`。那个“静态与动态,或:规则与如何打破它们”的弱点列表真的触动了我。我的意思是,我并不讨厌静态语言,但它在像 Javascript 这样的语言中没有任何意义。为什么导入不能简单地返回一个 promise,如果你想要当前的行为,你只需要写 `await import x`(没错,那是 ES7,但这不会太久)。我的意思是,导出方面看起来还不错(我不明白为什么它必须如此严格,但我也不期望出现任何真正的问题),只是导入方面很糟糕。像“你不能在 try catch 中使用 import”这样的内容就足以成为完全不使用 import 的理由... :S 唉。

    下一篇文章已经成为最后一篇文章,这是个很大的浪费,但回想起来,你确实覆盖了大部分需要覆盖的内容,是的。

    2015 年 8 月 15 日 上午 9:03

  6. Thodoris Greasidis

    没有关于 es6-module-loader 的评论
    以及 System.import :(
    曾经有一段时间它是 ES6 草案的一部分。
    http://www.2ality.com/2014/09/es6-modules-final.html

    2015 年 8 月 15 日 上午 9:41

    1. Jason Orendorff

      我确实链接到了关于此的持续工作(Loader 标准)。

      它只是不在 ES6 中,仅此而已!

      2015 年 8 月 17 日 上午 1:05

  7. Eduncle

    感谢您关于 ES6 的这篇文章。我希望您的下一篇文章能详细介绍代码拆分和模块,因为这方面非常需要。

    2015 年 8 月 17 日 上午 5:05

  8. Vladimir Starkov

    有人能解释一下,当浏览器原生支持 ES 模块时,将来是否还需要转译?

    2015 年 8 月 17 日 上午 5:48

  9. Šime Vidas

    模块可以同时从另一个模块导入默认导出和其他命名导出吗?

    // 例如
    import { each, map, default as _ } from ‘lodash’;

    2015 年 8 月 17 日 上午 10:39

    1. Vladimir Starkov

      你可以这样写

      import _, { each, map } from ‘lodash’;

      2015 年 8 月 17 日 上午 10:59

    2. Jason Orendorff

      是的。你的例子和 Vladimir 的例子是等价的。

      2015 年 8 月 17 日 下午 12:18

  10. Rich Brown

    很棒的文章,关于 ES6 模块我有一个后续问题

    我想编写一个通信“模块”,它负责建立 TLS 连接、向另一端进行身份验证、将 JSON 转换为 XML 以在网络上发送、跟踪各种内部状态等等,同时向程序的其余部分公开一个非常简单的 Open/Close/Set/Get API。

    我强烈地想要将这个“模块”拆分成单独的(短)文件,以便 TLS 连接处理可以与 JSON XML 代码等等分开。

    最好的方法是什么?非常感谢!

    2015 年 8 月 18 日 上午 4:34

  11. Jury Xiong

    我已经将这篇文章翻译成中文了,希望得到您的许可。

    http://www.pwhack.me/post/2015-08-18

    2015 年 8 月 18 日 上午 6:02

  12. Cédric

    很棒的文章,很棒的系列,谢谢您!

    快速提问。
    据我所知,我们必须这样做

    import * as Rx from ‘rx’;
    Rx.Oservable.fromArray();

    为什么不能这样做呢?

    import * from ‘rx’;
    Observable.fromArray();

    我们可以使用 “import {Observable} from ‘rx'”,但当模块导出很多东西时,这很快就会变得很麻烦...

    2015 年 8 月 19 日 上午 2:35

    1. Jason Orendorff

      这种类型的import * 让你无法仅通过查看源代码来确定导入的名称。

      如果你在一个文件中使用两个import * 声明,可能会出现命名冲突(两个模块都导出了相同的名称)。

      如果import * 声明导入的名称与全局变量(如Promise)相同,它只会覆盖全局变量,并且会不清楚发生了什么。

      更糟糕的是,导入的名称可能会在*没有任何代码更改*的情况下发生变化。因此,如果现在一切正常,如果这些导入模块中的任何一个在以后添加了与模块中其他内容同名的功能,它就可能随时中断。(我真的认为添加功能不应该是潜在的破坏性更改!)

      出于所有这些原因,import * 在支持它的语言(如HaskellPython)中有点不受欢迎。幸运的是,在 JS 中,我们已经(作为一个通用的规则)试图通过使用像Rx这样的命名空间对象来保持我们的命名空间整洁。我们有良好的习惯。因此,虽然import * 已经讨论过,但设计者决定不需要它。

      (有趣的是,Python 的风格指南说“对于通配符导入,有一个合理的用例,就是将内部接口作为公共 API 的一部分重新发布”。ES6 模块确实支持这一点,但语法不同:export * from "blah";。)

      2015 年 8 月 21 日 上午 7:17

  13. Caridy Patiño

    我来晚了,但为了澄清,有些说明

    @voracity 我们正在研究用于导入声明的 async/await。Node 的普遍问题是顶层 await 在很多方面都有问题,恐怕必须做出改变,这只是因为任何带有顶层 await 的模块的消费者都会搞砸 Node 的整个同步加载过程(即使你坚持使用 `require`)。我们会拭目以待。

    @David Mulder,你今天无法使用普通的脚本标签进行 try/catch,除非你手动加载和执行 eval(这会受到其他限制),在这方面没有变化。你可以在像 Node 这样的系统中进行 try/catch(例如:try/catch 一个 require 调用),这在浏览器环境中没有帮助。请记住,你将能够使用命令式 API 在顶层拥有更多控制权:`System.loader.import('foo').catch(...)`。

    @Thodoris Greasidis,`System.loader.import()` 将是用于从普通脚本加载和执行模块的命令式形式(我们正在为其制定规范)。

    @Vladimir Starkov,在可预见的未来,我们将继续使用转译,但不要将转译与捆绑混淆。从 JS 到 JS 的转译仍然有用,作为一种使用语言的新功能、用户端语言的扩展(例如 JSX)、优化(例如 uglify)等的方式。另一方面,捆绑只是允许我们加载和执行各种模块格式作为单个脚本。这可能是浏览器发布模块和加载器后将被重塑的部分,也许会更多地关注折叠模块而不是将它们捆绑成脚本。

    @Cédric,这很模糊,现在导入者必须知道两件事,模块标识符(`rx`)和标识符 `Observable`,它们必须在导出模块中定义。这也会成为重构的风险。

    2015 年 8 月 20 日 下午 4:32

  14. Christian Bankester

    类声明不会被提升,所以你上面的例子

    “`
    export {detectCats, Kittydar};

    // 此处不需要 `export` 关键字
    function detectCats(canvas, options) { … }
    class Kittydar { … }
    “`

    不会起作用,对吧?

    2015 年 8 月 21 日 上午 9:36

    1. Jason Orendorff

      类名在整个模块中可见——名称 被提升到封闭作用域的顶部。但类的实际创建并没有被提升。只有在类声明运行时才会发生。

      所以这个例子会起作用,只要我们没有在类声明运行之前以某种方式调用 `detectCats()`。

      如果你导入这个模块,这个类声明将在你的代码之前运行,所以应该没问题。

      2015 年 8 月 21 日 上午 10:01

  15. Owen Densmore

    我喜欢模块。但是存在问题。

    1 - 与 es5 的互操作性。当你在 babel 中使用模块时,它会产生依赖关系。将此代码放入 babel repl 中
    import foo from 'foo.js'
    var lib = {}
    export default lib

    2 - import 公开模块的文件位置,除非你进行了大量的 webpack/jspm 配置。是的,es5 存在标签地狱,但至少我们理解它!

    3 - Webpack/jspm:天哪,不要再有工作流程了!两者都尝试“捆绑”,但我不知道它是否有效。

    4 - 所有这些导致我的项目组成员避免使用模块,只使用 babel 和标准的 es5 模块,以及大量的标签。几乎没有进展!

    我很想读一篇关于模块加载的文章。

    2015 年 8 月 21 日 上午 11:44

    1. Filipe Silva

      我一直将一个项目转换为 webpack+babel,我发现模块加载确实是一个福音。路径是相对的,这在保持模块化并与项目之间共享方面帮助很大。

      2015 年 8 月 21 日 下午 12:01

    2. Jason Orendorff

      我不会否认存在问题。我们正在使用已经完成的系统的一半(语法)。另一半(标准动态加载 API,加上加载器自定义)还没有准备好。

      社区构建的编译器、库和工具能够将所有东西整合到一个连贯的系统中,这实际上非常令人印象深刻,考虑到事情处于半途状态。

      但让我逐点解决你的问题。

      1 - 我猜 在线 Babel 游乐场 被配置为生成 CommonJS 模块。但这 是可配置的

      如果你不想自己配置它,使用 webpack,所有事情都会正常工作。webpack 会告诉 Babel 要输出哪种模块格式,并自动将其与它所需的 20 行左右的支持代码捆绑在一起。

      2 - 这还没有标准化,所以现在,每个 ES6 模块编译器都可以做任何它想做的事情。

      最终标准可以是任一种。我猜我想文件名会赢。

      我想按包名而不是文件名导入包。但我几乎与谈论过这件事的每个人都发现文件名更直观。在包内,我不得不承认文件名很有道理。

      但是,如果你坚持不使用文件名,我无法理解也不想配置你的加载器。这种映射必须以某种方式生成。像 Java 和 Python 这样的语言(Node 也是这样)可以避免每次你想导入东西时都搜索无数个不同的目录。网络不是这样的。当你决定获取一个 URL 并等待它进来时,它最好是正确的。

      3 - 我知道——但它对我有用,我被说服了。如果你今年勉强地选择了一个工具,那就选 webpack。

      Larry Wall 曾经称懒惰为编程的三大美德之一。但他警告不要沉溺于虚假懒惰。真正懒惰的人会不遗余力地消除工作。

      4 - 进度缓慢是因为标准化工作进展缓慢。这是我们必须解决的根本问题。

      2015 年 8 月 21 日 下午 8:14

      1. Owen Densmore

        感谢 Jason 的清晰、周到的回复。

        2015 年 9 月 1 日 上午 10:55

  16. Brian De Sousa

    好文章!我刚接触到这个系列…… 一定要回去阅读系列中的前几篇文章。谢谢!

    2015 年 8 月 28 日 下午 4:26

本文的评论已关闭。