深入ES6 模块系统

本文转载自:众成翻译

译者:neck

链接:http://www.zcfy.cc/article/4436

原文:https://ponyfoo.com/articles/es6-modules-in-depth#the-es6-module-system

ES6 模块系统

在ES6之前,我们用自己的方式来在 JavaScript 中实现模块。很长一段时间以来,像 RequireJS、Angular 的依赖注入和 CommonJS 这样的系统,配合着一些有用的工具,比如 Browserify 和 Webpack,一直在解决我们的需求。然而,到了2015 年,一个标准的模块系统早就应该发布了。我们马上就会看到,你很快会注意到 ES6 模块受到了 CommonJS 的很大影响。我们将查看exportimport语句,从中会看到ES6模块和CommonJS有多一致,同时,我们将会在这篇文章中讨论它们。

今天我们将介绍 ES6 模块系统的几个方面。

严格模式

在 ES6 模块系统中, 严格模式默认被开启。如果你不知道严格模式是什么, 它只是语言的一个更严格的版本它让语言的很多不好的部分都消失了。它使编译器可以通过在用户代码中禁止使用一些不可靠的语法来表现得更好。下面是对 MDN 上的严格模式文章中所记录的更改的总结。

  • 变量不能未声明就使用

  • 函数参数必须有唯一的名称 (否则会被认为是语法错误)

  • with 语句被禁止使用

  • 赋值给只读属性会抛出一个错误

  • 00840 这样的八进制数是语法错误

  • 尝试 delete 不可删除的数据会抛出一个错误

  • delete prop 被认为是语法错误, 只能删除属性 delete global[prop]

  • eval 不会引入新的变量到它的作用域

  • evalarguments 的绑定不会被改变

  • 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。

具名的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 fooexport 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 很像,它只能定义在模块的顶级作用域。这可以帮助转换工具实现它们的模块加载功能,并帮助其它静态分析工具解析你的代码库。

导入默认的Exports

在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和浏览器实现。你可以从一个模块中exportimport绑定的多种方法,但这并不多,因为它们增加了复杂性,但是时间将会告诉你,所有额外的API是否和它的庞大一样方便。

深入ES6 模块系统的更多相关文章

  1. NodeJS模块和ES6模块系统语法及注意点

    社区模块规范: 1.CommonJS规范 规范实现者: NodeJS 服务端 Browserify 浏览器 2.AMD规范 全称 异步模块定义 规范实现者: RequireJS 浏览器 3.CMD规范 ...

  2. Typescript 实战 --- (9)ES6与CommonJS的模块系统

    1.ES6模块系统 1-1.export 导出 (1).单独导出 // a.ts export let a = 1; (2).批量导出 // a.ts let b = 2; let c = 3; ex ...

  3. 全面解析ECMAScript 6模块系统

    快速使用Romanysoft LAB的技术实现 HTML 开发Mac OS App,并销售到苹果应用商店中.   <HTML开发Mac OS App 视频教程> 土豆网同步更新:http: ...

  4. ES6 的模块系统

    原文地址:https://hacks.mozilla.org/2015/08/es6-in-depth-modules/ ES6 是 ECMAScript 第 6 版本的简称,这是新一代的 JavaS ...

  5. ES6中export , export default , import模块系统总结

    最近在学习使用Webpack3的时候发现,它已经可以在不使用babel的情况下使用ES6的模块加载功能了. 说到ES6的模块加载功能,我们先复习一下CommonJS规范吧: 一  . CommonJS ...

  6. ES6模块import细节

    写在前面,目前浏览器对ES6的import支持还不是很好,需要用bable转译. ES6引入外部模块分两种情况: 1.导入外部的变量或函数等: import {firstName, lastName, ...

  7. ES6模块的import和export用法总结

    ES6之前以前出现了js模块加载的方案,最主要的是CommonJS和AMD规范.commonjs前者主要应用于服务器,实现同步加载,如nodejs.AMD规范应用于浏览器,如requirejs,为异步 ...

  8. commonjs模块和es6模块的区别

    commonjs模块与es6模块的区别 到目前为止,已经实习了3个月的时间了.最近在面试,在面试题里面有题目涉及到模块循环加载的知识.趁着这个机会,将commonjs模块与es6模块之间一些重要的的区 ...

  9. ES6模块之export和import详解

    ES6中的模块即使一个包含JS代码的文件,在这个模块中所有的变量都是对其他模块不可见的,除非我们导出它.ES6的模块系统大致分为导出(export)和导入(import)两个模块. 1.模块导出(ex ...

随机推荐

  1. VS 2017 统计项目代码总行数

    编辑 → 查找和替换 → 在文件中的查找,打开查找窗口 填入正则表达式  ^b*[^:b#/]+.*$ 查找范围选“整个解决方案”,勾选上“使用正则表达式” 如果要限制文件类型,就填上要查找的文件类型 ...

  2. freemarker使用map替换字符串中的值

    package demo01; import java.io.IOException;import java.io.OutputStreamWriter;import java.io.StringRe ...

  3. Linux+Apache下如何安装SSL证书

    最近很多站长在问linux系统平台下如何安装SSL证书?Linux+Apache下如何安装SSL证书?本文整理了关于Linux+Apache下如何安装SSL证书的相关教程供大家参考,更多SSL证书安装 ...

  4. js声明变量作用域会提前

    var s = 1; function test() { console.info(s); var s = 2; console.info(s); } test(); >>>unde ...

  5. 多态(day10)

    二十二 多态(Polymorphic) 函数重写(虚函数覆盖).多态概念 如果将基类中的某个成员函数声明为虚函数,那么子类与其具有相同原型的成员函数就也将是虚函数,并且对基类中的版本形成覆盖. 这时, ...

  6. [luogu2825 HEOI2016/TJOI2016] 游戏 (二分图最大匹配)

    传送门 Description 在2016年,佳缘姐姐喜欢上了一款游戏,叫做泡泡堂.简单的说,这个游戏就是在一张地图上放上若干个炸弹,看是否能炸到对手,或者躲开对手的炸弹.在玩游戏的过程中,小H想到了 ...

  7. Nginx学习总结(1)——Nginx入门简介

    本文主要介绍一些Nginx的最基本功能以及简单配置,但不包括Nginx的安装部署以及实现原理.废话不多,直接开始. 1.静态HTTP服务器 首先,Nginx是一个HTTP服务器,可以将服务器上的静态文 ...

  8. mysql 服务器监控系列-黄杉 mysqldba

    http://blog.csdn.net/mchdba/article/category/2220809

  9. 《coredump问题原理探究》Linux x86版7.7节 set对象

    看一下bits/stl_map和bits/stl_set能够看到map和set的定义例如以下: 84 template <typename _Key, typename _Tp, typenam ...

  10. ubuntu14.04上搭建android开发环境

    这几天心血来潮,想在ubuntu上写写android软件.所以就上网找些资料在ubuntu上搭建android环境.结果要么时不完整的,要么就是过时的. 所以我把我搭建android环境的过程写下了, ...