本文转载自:众成翻译
译者:neck
连接:http://www.zcfy.cc/article/4436
原文:https://ponyfoo.com/articles/es6-modules-in-depth#the-es6-module-systemes6
在ES6以前,咱们用本身的方式来在 JavaScript 中实现模块。很长一段时间以来,像 RequireJS、Angular 的依赖注入和 CommonJS 这样的系统,配合着一些有用的工具,好比 Browserify 和 Webpack,一直在解决咱们的需求。然而,到了2015 年,一个标准的模块系统早就应该发布了。咱们立刻就会看到,你很快会注意到 ES6 模块受到了 CommonJS 的很大影响。咱们将查看export
和 import
语句,从中会看到ES6模块和CommonJS有多一致,同时,咱们将会在这篇文章中讨论它们。api
今天咱们将介绍 ES6 模块系统的几个方面。数组
在 ES6 模块系统中, 严格模式默认被开启。若是你不知道严格模式是什么, 它只是语言的一个更严格的版本它让语言的不少很差的部分都消失了。它使编译器能够经过在用户代码中禁止使用一些不可靠的语法来表现得更好。下面是对 MDN 上的严格模式文章中所记录的更改的总结。
变量不能未声明就使用
函数参数必须有惟一的名称 (不然会被认为是语法错误)
with
语句被禁止使用
赋值给只读属性会抛出一个错误
像 00840
这样的八进制数是语法错误
尝试 delete
不可删除的数据会抛出一个错误
delete prop
被认为是语法错误, 只能删除属性 delete global[prop]
eval
不会引入新的变量到它的做用域
eval
和 arguments
的绑定不会被改变
arguments
不会神奇地跟踪方法参数的变化
再也不支持arguments.callee
,使用它会抛出 TypeError
再也不支持arguments.caller
,使用它会抛出 TypeError
上下文做为 this
在方法调用时不会被强制包装成一个 Object
(译者注:即this不会指向全局对象)
再也不可以使用 fn.caller
and fn.arguments
访问 JavaScript 的堆栈
保留字(例如 protected
, static
, interface
等等)不能被做为新变量声明
若是这些规则对你来讲不是显而易见的,你应该使用 'use strict'
在每个地方。尽管在 ES6 中已经成为事实,但在 ES6 中使用 'use strict'
仍然是一种很好的作法。我已经使用严格模式很长时间了,而且毫不会用回原来的模式!
如今让咱们了解export
,咱们的第一个 ES6 模块关键字!
export
在 CommonJS 中,你将值暴露在module.exports
上来导出它们。正以下面的代码片断所示,您能够导出任何内容像是基本类型、对象、数组或函数。
module.exports = 1 module.exports = NaN module.exports = 'foo' module.exports = { foo: 'bar' } module.exports = ['foo', 'bar'] module.exports = function foo () {}
ES6模块系统将 export
封装成API,相似于 CommonJS的modules
。ES6 模块中的声明只做用于该模块,和使用 CommonJS 同样。这意味着,在模块中声明的任何变量都不能用于其余模块,除非它们明确地导出为模块 API 的一部分(而后导入到但愿访问它们的模块中)。
你能够经过把 module.exports =
变成 export default
来模拟咱们刚刚看到的CommonJS代码。
export default 1 export default NaN export default 'foo' export default { foo: 'bar' } export default ['foo', 'bar'] export default function foo () {}
与 CommonJS 不一样,导出语句只能放在 ES6 模块的最外层,而不能放在方法中,即便在加载模块时它们所在的方法会当即被调用。据推测,这种限制是为了让编译器更容易地解释 ES6 模块,可是这也是一个很好的限制,由于有不少很好的理由去以动态地定义和暴露 API的方式来调用方法。
function foo () { export default 'bar' // SyntaxError } foo()
你不仅可使用默认的Export,你还可使用具名的Exports。
在 CommonJS 中,你甚至不须要事先分配一个对象给 module.exports
。你能够把属性添加到它上面。无论 module.exports
最终的属性包含什么,它仍然是一个单独的绑定。
module.exports.foo = 'bar' module.exports.baz = 'ponyfoo'
咱们能够经过使用具名导出语法在 ES6 模块中复制上述内容,而不是像CommonJS同样将它分配给module.exports
。在ES6中,你能够声明要export
的绑定。注意,下面的代码不能重构为先声明变量再执行 export foo
,那将会致使一个语法错误。在这里,咱们看到了ES6模块如何经过声明式模块系统API的工做方式来支持静态分析。
export var foo = 'bar' export var baz = 'ponyfoo'
还有一个重要的点,是要记住咱们正在导出的是绑定。
重要的一点是,ES6 模块导出的是绑定,而不是值或引用。这意味着您导出的foo
变量将被绑定到模块上的foo
变量中,它的值将取决于对foo
的修改。 不过,我建议在最初加载模块以后,不要更改模块的公共接口。
若是你有一个./a
模块像下面这样,这导出的foo
将被绑定为'bar'
,持续500ms以后,foo
将绑定为 'baz'
export var foo = 'bar' setTimeout(() => foo = 'baz', 500)
除了默认绑定和单独绑定以外,你还能够导出一个绑定列表。
正以下面的代码片断所示,ES6 模块容许你导出已命名的位于顶级做用域的成员列表。
var foo = 'ponyfoo' var bar = 'baz' export { foo, bar }
若是你想要用其余名字来导出一个绑定,你可使用export { foo as bar }
语句,就像下面展现的这样。
`export { foo as ponyfoo }`
在使用export
的命名成员列表声明风格时,还可使用as default
。下面代码的做用和执行export default foo
和export bar
同样,只不过在一行语句而已。
`export { foo as default, bar }`
只在模块文件的底部使用export default
有不少好处。
export
最佳实践能够定义具名的Exports,能够导出一个具备别名的列表,还能够暴露一个默认的export
,这会致使一些混乱。在很大程度上,我鼓励大家使用export default
而且最好在模块文件的末尾使用。以下代码所示,你能够调用你的API 对象 api
或者将它命名为模块自己。
var api = { foo: 'bar', baz: 'ponyfoo' } export default api
第一,模块的导出接口当即变得明显。无需在模块中翻查并将各个部分组合在一块儿来计算 API,您只需滚动到最后。有一个清晰定义的 API 导出的地方,也能够更容易地解释模块导出的方法和属性。
第二,是应该使用 export default
仍是具名的导出又或者是列表的导出甚至是带有别名的导出,你不该该纠结这个。如今有一个指导方针,就是在任何地方都使用 export default
。
第三,一致性。 在CommonJS世界中,咱们一般从模块中导出一个方法,而后就能够了。而使用具名导出进行这样的操做是不可能的,由于你暴露了一个对象来表示该方法,除非你在导出列表中使用as default
。
第四,这其实是以前所提到的点的总结。export default
语句放在模块的底部,咱们当即能够很清晰的看出这个模块的API是什么、有哪些方法,可让模块的使用者能够很轻松的调用它的 API。当习惯于使用export default
并老是在模块的最后使用它,你会感到使用ES6的模块系统是无痛的。
如今咱们已经讨论了export
API 及其注意事项,让咱们开始讨论 import
语句。
import
这个语句是和export
相对的语句。首先,它们能够被用来从另外一个模块加载一个模块,这种加载模块的方式是特别实现的,目前尚未浏览器实现模块加载。聪明的人会在浏览器中解决模块加载问题,这样,你就能够当即编写符合标准的 ES6 代码。像 Babel 这样的转换工具能够在模块系统的帮助下像CommonJS同样链接模块。意味着在babel中,import
语句和CommonJS中的require
语句遵循同样的语义。
让咱们以 lodash
为例。下面的语句简单地从模块中加载 Lodash 模块。它并无建立任何变量,但它将可使用lodash
模块。
`import 'lodash'`
在导入绑定以前,让咱们来关注一下import
语句的实际状况。和export
很像,它只能定义在模块的顶级做用域。这能够帮助转换工具实现它们的模块加载功能,并帮助其它静态分析工具解析你的代码库。
在CommonJS中,你能够经过 require
语句import
一些代码,就像这样:
`var _ = require('lodash')`
要从ES6模块导入默认的导出绑定,你只须要为它指定一个名字。与声明一个变量相比,语法有点不一样,由于你正在导入一个绑定,并且可让它更利于静态分析工具的分析。
`import _ from 'lodash'`
你也能够导入具名的导出而且可使用别名。
这里的语法和咱们刚才使用的默认导出很是类似,只需添加一些大括号,而后选择任意指定的导出. 注意,这个语法相似于解构赋值语法,但也有一些不一样。
`import {map, reduce} from 'lodash'`
不一样于解构赋值的是,你可使用别名来重命名导入的绑定。你能够在你认为合适的状况下混合使用别名和非别名的导出。
`import {cloneDeep as clone, map} from 'lodash'`
你还能够混合和匹配指定的导出和默认导出。若是你想要它在括号里,你必须使用default
的名称,你能够为default
指定别名;或者你也能够将默认的导入与指定的导入列表混合在一块儿。
import {default, map} from 'lodash' import {default as _, map} from 'lodash' import _, {map} from 'lodash'
最后,还有import *
的语句
import
全部内容你还能够将一个模块导入为命名空间对象。它不导入指定的导出或默认值,而是导入全部的东西。注意,导入语法必须使用别名,其中全部绑定都将被替换到别名上。若是有一个默认的导出,将会被替换为alias.default
。
`import * as _ from 'lodash'`
上面的代码展现了这个语法。
注意,你能够在利用CommonJS模块的同时,经过babel编译器来使用ES6模块。最重要的是,你能够在CommonJS和ES6模块之间进行互操做。这意味着即便你导入了一个用CommonJs编写的模块,它也会起做用。
ES6模块系统看起来很棒,它是JavaScript中缺乏的最重要的东西之一。我但愿他们能很快找到一个最终完成的模块加载API和浏览器实现。你能够从一个模块中export
或import
绑定的多种方法,但这并很少,由于它们增长了复杂性,可是时间将会告诉你,全部额外的API是否和它的庞大同样方便。