ES6 的模块系统

此文为翻译,原文地址在此刻:https://hacks.mozilla.org/2015/08/es6-in-depth-modules/【转】

ES6 是 ECMAScript 第 6 版本的简称,这是初一代的 JavaScript 的规范。ES6
in Depth 是有关 ES6
的同等多重初特点的介绍。

遥想 2007 年,笔者开始当 Mozilla 的 JavaScript
团队工作之时刻,那个时刻典型的 JavaScript 程序只发一行代码。

少数年之后, Google Map 被揭示。但是当那之前不久,JavaScript
的主要用途还是表单验证,当然啦,你的<input onchange=>微机平均来说只是出一行。

事过情迁,JavaScript
项目就转移得十分大,社区也更上一层楼产生了有促进开发而扩大程序的工具。首先你要之就是模块系统。模块系统被您得将您的行事分散在不同之文书与目录中,让其前可互相走访,并且可以非常管用地加载它们。自然而然地,JavaScript
发展起了模块系统,事实上是基本上单模块系统(AMD,CommonJS,CMD,译者注)。不仅如此,社区还提供了保险管理工具(NPM,译者注),让您可以安装与拷贝高度依赖其他模块的软件。也许你见面以为,带有模块特性的
ES6,来得稍晚矣。

模块基础

一个 ES6 的模块是一个含有了 JS 代码的公文。ES6
里从未所谓的 module 关键字。一个模块看起就是和一个日常的台本文件一律,除了以下简单独区分:

  • ES6 的模块自动启严格模式,即使你没写 'use strict'

  • 若得以模块中动用 import 和 export

于我们先来瞧 export。在模块中声称的其他东西都是默认私有的,如果您想对另外模块
Public,你不能不 export 那有代码。我们来几种实现方式,最简易的道是添加一个 export 关键字。

// kittydar.js - Find the locations of all the cats in an image.
// (Heather Arthur wrote this library for real)
// (but she didn't use modules, because it was 2013)

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

export class Kittydar {
  ... several methods doing image processing ...
}

// This helper function isn't exported.
function resizeCanvas() {
  ...
}
...

您得以 functionclassvarlet 或 const 前添加 export

如您想写一个模块,有这些虽够了!再为无用把代码放在 IIFE
或者一个回调函数里了。既然您的代码是一个模块,而无下论文件,那么你命的凡事都见面于封装进模块的作用域,不再会发生超越模块或超文件的全局变量。你导出的声明部分则会成为这个模块的
Public API。

除外,模块里之代码和一般性代码没啥异常分别。它可拜有中心的全局变量,比如 Object 和 Array。如果你的模块跑在浏览器里,它用可以看 document 和 XMLHttpRequest

当另外一个文件中,我们好导入这个模块并且动用 detectCats() 函数:

// demo.js - Kittydar demo program

import {detectCats} from "kittydar.js";

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

苟导入多独模块中之接口,你可如此形容:

import {detectCats, Kittydar} from "kittydar.js";

当您运行一个涵盖 import 声明的模块,被引入的模块会优先给导入并加载,然后因依赖关系,每一个模块的情节会动深度优先的标准化进行遍历。跳过已经执行了之模块,以此避免因循环。

立马即是模块的基础部分,挺简单的。

导出表

万一您当以每个要导出的一部分前都勾上 export 很麻烦,你得仅写一行你想只要导出的变量列表,再就此花括号包起来。

export {detectCats, Kittydar};

// no `export` keyword required here
function detectCats(canvas, options) { ... }
class Kittydar { ... }

传输出表不自然要是起于文书之第一执,它可以出现于模块顶级作用域中之其他一样实行。你可以描绘多只导出表,也堪当列表中更写及任何 export 声明,只要没有变量名吃再度导出即可。

重名命导出同导入

倘导入的变量名为正与您模块中的变量名闯了,ES6
允许而叫你导入的物还命名:

// suburbia.js

// Both these modules export something named `flip`.
// To import them both, we must rename at least one.
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";
...

看似地,你当导出变量的时段也克重命名。这个特点在您想用同一个变量名导出些许不成的场景下充分有益于,举个栗子:

// unlicensed_nuclear_accelerator.js - media streaming without drm
// (not a real library, but maybe it should be)

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

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

默认导出

初一代之正统的规划意见是匹配现有的 CommonJS 和 AMD 模块。所以若你生一个
Node 项目,并且刚刚施行完 npm install lodash,你的 ES6
代码可以单独引入 Lodash 中之函数:

import {each, map} from "lodash";

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

只是如果您已经习以为常了 _.each 或者押无展现 _ 的讲话就是浑身不爽,当然然使
Lodash 也是毋庸置疑的点子。

这种情形下,你可略改变一下你的 import 写法,不写花括号:

import _ from "lodash";

斯简写等价于 import {default as _} from "lodash";。所有 CommonJS 和
AMD 模块于被 ES6 代码应用的当儿都早就闹矣默认的导出,这个导出和而于
CommonJS 中 require() 得到的物是均等的,那即便是 exports 对象。

ES6 的模块系统被设计成让您可一次性引入多个变量。但对于已经存在的
CommonJS
模块来说,你会获得的只有默认导出。举个栗子,在写之文的常,据笔者所掌握,著名的 colors 模块没有特别支持
ES6。这是一个出于多个 CommonJS 模块组合的模块,正使 npm
上之那些管。然而你依然得以一直将该引入到公的 ES6 代码中。

// ES6 equivalent of `var colors = require("colors/safe");`
import colors from "colors/safe";

倘您想写好的默认导出,那吧坏粗略。这个中连没有什么大科技,它和普通的导出没什么两类,除了它的导出名是 default。你可以我们前早已介绍过的语法:

let myObject = {
  field1: value1,
  field2: value2
};
export {myObject as default};

如此这般重复好:

export default {
  field1: value1,
  field2: value2
};

export default 关键字后得以从任何价值:函数,对象,对象字面量,任何你能够说得起底事物。

模块对象

抱歉,这篇稿子的始末有点多,但 JavaScript
已经算好的了:因为部分原因,所有语言的模块系统还出平等充分堆没什么卵用的特性。所幸的是,咱们就发生一个话题要讨论了,呃,好吧,两个。

import * as cows from "cows";

当你 import *,被引入进来的是一个 module namespace object。它的属性是坏模块的导出,所以只要
“cows” 模块导出了一个叫吧 moo() 的函数,当您如这么引入了 “cows”
之后,你可如此描写 cows.moo()

会合模块

偶尔一个保证的主模块会引入博其他模块,然后再次将她以一个联的艺术导出。为了简化这样的代码,我们发一个
import-and-export 的简写方法:

// world-foods.js - good stuff from all over

// import "sri-lanka" and re-export some of its exports
export {Tea, Cinnamon} from "sri-lanka";

// import "equatorial-guinea" and re-export some of its exports
export {Coffee, Cocoa} from "equatorial-guinea";

// import "singapore" and export ALL of its exports
export * from "singapore";

这种 export-from 的表达式和后与了一个 export 的 import-from 表达式类似。但与真正的导入不同,它并无见面在公的作用域中参加二次等导出的变量绑定。所以若你打算于 world-foods.js 写用到了 Tea 的代码,就变更以这简写形式。

若是 “singapore”
导出底之一一个变量恰巧和其它的导出变量名闯了,那么这里就是见面产出一个荒谬。所以您应当三思而行用 export *

Whew!我们介绍完语法了,接下去进入有趣的环。

import 到底干了什么

啥吧未尝涉及,信不信由而。

哦,你好像看起没那么好骗。好吧,那尔相信专业几乎从不言语到 import 该做什么呢?你以为就是一致起善事还是帮倒忙为?

ES6
将模块的加载细节净交给了贯彻,其余的尽有则规定得杀详尽。

约来说,当 JS 引擎运行一个模块的时,它的行为大致可概括为以下四步:

  1. 解析:引擎实现会晤看模块的源码,并且检查是不是发生语法错误。

  2. 加载:引擎实现会晤(递归地)加载所有为引入的模块。这片咱们还从来不条件。

  3. 链接:引擎实现会晤吧每个新加载的模块创建一个作用域,并且用模块中的声明绑定填入内,包括自旁模块中引入的。

当你尝试 import {cake} from "paleo" 但是 “paleo”
模块并没导出叫 cake 的事物上,你呢会当这获得错误。这万分不好,因为您离开实践
JS,品尝 cake 只差一步了!

  1. 行:终于,JS
    引擎开始实施刚加载进来的模块中的代码。到者时段,import 的处理过程已经完结,因此当
    JS 引擎执行到一行 import 声明的上,它什么呢不会见提到。

总的来看了未?我说了 import
“啥吧未尝涉及”,没骗你吧?有关编程语言的整肃话题,哥从不说谎。

然而,现在咱们得介绍这个系统受到有趣之片段了,这是一个充分可怜的
trick。正缘这个系统并从未点名加载的底细,也坐您只待看无异目源码中的 import 声明就足以以运行前打明白模块的因,某些
ES6
的贯彻还好通过事先处理便水到渠成有的行事,然后用模块全部起包改成一个文书,最后经网络分发。像 webpack 这样的家伙就是开此事情的。

立特别之伟,因为通过网加载资源是格外耗时的。假设你要一个资源,接着发现内部来 import 声明,然后您同时得请又多之资源,这同时见面耗费更多之时日。一个
naive 的 loader 实现可能会见倡导许多不好网络要。但生了
webpack,你不仅可于今日尽管起利用
ES6,还好获取方方面面模块化的补并且不往运行时性妥协。

本我们计划过一个缕定义之 ES6
模块加载规范,而且我们举行出来了。它从不成最终正式的故之一是它们无法和包装这同特点调和。模块系统要吃规范,打包也未应有为放弃,因为它极好了。

动态 VS 静态,或者说:规矩与什么打破规矩

当同样家动态编程语言,JavaScript 令人好奇地有着一个静态的模块系统。

  • import 和 export 只能写在一流作用域中。你无法在规范语句被运用引入和导出,你呢未可知在公写的函数作用域中使import

  • 具备的导出必须出示地指定一个变量曰,你吧无能为力通过一个循环动态地引入一堆放变量。

  • 模块对象吃包裹起来了,我们无能为力透过 polyfill 去 hack 一个新 feature。

  • 在模块代码运行之前,所有的模块都必须经历加载,解析,链接的历程。没有可推加载,惰性 import 的语法。

  • 对于 import 错误,你无法以运转时进行
    recovery。一个施用或含有了几百单模块,其中的其它一个加载失败或链接失败,这个动用就是未会见运作。你无法在 try/catch 语句中 import。(不了正为
    ES6 的模块系统是这样地静态,webpack
    可以于预先处理时即便为您检测出这些不当)。

  • 若莫办法 hook
    一个模块,然后以它吃加载之前运行而的有些代码。这代表模块无法控制它的倚重是怎给加载的。

苟你的需要还是静态的话,这个模块系统还是杀 nice 的。但你要么想念 hack
一下,是啊?

即就算是也啥你利用的模块加载系统或会见供 API。举个栗子,webpack 有一个
API,允许你 “code
splitting”,按照卿的需求去惰性加载模块。这个 API
也能协助您打破地方列有之保有规矩。

ES6
的模块是好静态的,这十分好——许多精的编译器工具因此收入。而且,静态的语法已经让设计成可以和动态的,可编程的
loader API 协同工作。

我何时能开应用 ES6 模块?

只要您今天就要起运用,你需要像 Traceur 和 Babel 这样的预先处理工具。这个系列专题之前也时有发生文章介绍了安使
Babel 和
Broccoli 去生成可用于
Web 的 ES6 代码。那篇稿子的板栗也叫开源在了 GitHub
上。笔者的马上首文章也介绍了什么样使
Babel 和 webpack。

ES6 模块系统的要设计者是 Dave Herman 和 Sam
Tobin-Hochstadt,此二人数不顾包括笔者在内的数位委员之反对,始终坚持如今公看到的
ES6 模块系统的静态部分,争论长及数年。Jon Coppeard 正在火狐浏览器上贯彻
ES6 的模块。之后包括 JavaScript Loader
规范在内的劳作曾当开展着。HTML
中类似 <script type=module> 这样的物后为会见跟大家见面。

这便是 ES6 了。

迎接大家对 ES6 进行吐槽,请期待下周 ES6 in
Depth 系列之下结论文章。