ECMAScriptES6 的模块系统

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

ES6 是 ECMAScript 第 6 版本的简称,那是新一代的 JavaScript 的业内。ES6
in Depth
 是有关 ES6
的一各种新特性的牵线。

遥想 2005 年,我伊始在 Mozilla 的 JavaScript
团队工作的时候,这个时候典型的 JavaScript 程序唯有一行代码。

两年之后, 谷歌 Map 被揭晓。可是在那以前不久,JavaScript
的首要用途还是表单验证,当然啦,你的<input onchange=>电脑平均来说唯有一行。

事过情迁,JavaScript
项目现已变得那么些高大,社区也迈入出了部分牵动开发可伸张程序的工具。首先你需求的便是模块系统。模块系统让你能够将您的做事分散在区其他文本和目录中,让它们此前能够互相走访,并且可以13分管用地加载它们。任其自然地,JavaScript
发展出了模块系统,事实上是多个模块系统(AMD,CommonJS,CMD,译者注)。不仅如此,社区还提供了包管理工具(NPM,译者注),让你可以安装和拷贝中度爱惜其余模块的软件。可能你会以为,带有模块本性的
ES6,来得有个别晚了。

模块基础

一个 ES6 的模块是三个暗含了 JS 代码的公文。ES6
里从未所谓的 module 关键字。3个模块看起来就和三个一般性的剧本文件一律,除了以下七个分别:

  • 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

在别的2个文本中,大家得以导入这几个模块并且使用 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";

当您运营3个富含 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";
...

接近地,你在导出变量的时候也能重命名。这些特点在你想将同1个变量名导出三遍的风貌下十二分有利于,举个栗子:

// 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 模块。所以一旦你有3个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 和
英特尔 模块在被 ES6 代码应用的时候都早已有了暗许的导出,这些导出和您在
CommonJS 中 require() 拿到的事物是均等的,那就是 exports 对象。

ES6 的模块系统被设计成让你可以一遍性引入两个变量。但对此早已存在的
CommonJS
模块来说,你能博取的唯有默许导出。举个栗子,在撰文此文之时,据作者所知,盛名的 colors 模块没有特别扶助ES6。那是2个由多少个 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()

聚拢模块

偶然三个包的主模块会引入许多任何模块,然后再将它们以1个联结的法门导出。为了简化那样的代码,大家有一个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 的表明式和前面跟了2个 export 的 import-from 表明式类似。但和确实的导入不一致,它并不会在您的功能域中进入一次导出的变量绑定。所以只要您打算在 world-foods.js 写用到了 Tea 的代码,就别使用那几个简写方式。

设若 “singapore”
导出的某二个变量恰巧和任何的导出变量名冲突了,那么那里就会产出四个错误。所以你应该严峻接纳 export *

Whew!大家介绍完语法了,接下去进入有趣的环节。

import 到底干了啥

啥也没干,信不信由你。

哦,你就好像看起来没那么好骗。好啊,那您相信专业大致没有谈到 import 该做什么样啊?你认为那是一件善事还是帮倒忙呢?

ES6
将模块的加载细节一齐交给了贯彻,其他的实施部分则规定得十分详尽

粗粗来说,当 JS 引擎运行1个模块的时候,它的行为大致可回顾为以下四步:

  1. 分析:引擎完结会阅读模块的源码,并且检查是或不是有语法错误。

  2. 加载:引擎已毕会(递归地)加载全体被引入的模块。那有个别咱还没规范。

  3. 链接:引擎达成会为种种新加载的模块创设2个功效域,并且将模块中的表明绑定填入其中,包含从其余模块中引入的。

当你品味 import {cake} from "paleo" 可是 “paleo”
模块并没有导出叫 cake 的事物时候,你也会在那时收获错误。那很不佳,因为你离实践
JS,品尝 cake 只差一步了!

  1. 举行:终于,JS
    引擎早先实行刚加载进来的模块中的代码。到这些时候,import 的处理进程已经做到,因此当
    JS 引擎执行到一行 import 注明的时候,它什么也不会干。

见到了不?小编说了 import
“啥也没干”,没骗你吧?有关编程语言的威严话题,哥从不说谎。

可是,未来大家可以介绍这几个系统中有趣的一部分了,那是三个那几个酷的
trick。正因为那些连串并没有点名加载的细节,也因为你只要求看一眼源码中的 import 表明就可以在运维前搞了然模块的依靠,某个ES6
的达成甚至可以通过预处理就完了具有的做事,然后将模块全体打包成贰个文件,最终经过互联网分发。像 webpack 那样的工具就是做那么些业务的。

那丰盛的伟大,因为经过网络加载能源是那一个耗时的。借使你请求3个能源,接着发现中间有 import 表明,然后你又得请求越来越多的能源,那又会消耗更加多的岁月。一个naive 的 loader 落成大概会倡导许数十三回互连网请求。但有了
webpack,你不光可以在今天就起来采用ES6,还足以拿走方方面面模块化的补益并且不向运转时质量和平解决。

原本我们安插过一个详尽定义的 ES6
模块加载规范,而且我们做出来了。它从不成为最终标准的缘故之一是它不能与包装这一表征调和。模块系统须要被规范,打包也不应当被扬弃,因为它太好了。

动态 VS 静态,或许说:规矩和什么打破规矩

作为一门动态编程语言,JavaScript 令人惊异地具有1个静态的模块系统。

  • import 和 export 只好写在一级效用域中。你无法在标准化语句中拔取引入和导出,你也无法在你写的函数功效域中行使import

  • 持有的导出必须出示地指定3个变量名,你也无力回天通过多个循环往复动态地引入一堆变量。

  • 模块对象被卷入起来了,我们无法透过 polyfill 去 hack 二个新 feature。

  • 在模块代码运维以前,全部的模块都必须经历加载,解析,链接的进度。没有得以延缓加载,惰性 import 的语法。

  • 对于 import 错误,你不可能在运维时进行recovery。1个使用或者带有了几百个模块,其中的任何三个加载失利或链接退步,那么些应用就不会运作。你无法在 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 和 萨姆Tobin-Hochstadt,此2人不顾包括我在内的数位委员的反对,始终坚定不移近期你看看的
ES6 模块系统的静态部分,争辨长达数年。Jon Coppeard 正在火狐浏览器上达成ES6 的模块。之后包罗 JavaScript Loader
规范
在内的做事早已在展开中。HTML
中好像 <script type=module> 那样的事物之后也会和豪门照面。

这便是 ES6 了。

欢迎大家对 ES6 举行吐槽,请期待前一周 ES6 in
Depth
 体系的统计小说。