在 ES6 以前,咱们已经知道了 RequireJS,AngularJS 的依赖注入,以及 CommonJS,具体能够看笔者的上一篇文章《JS模块化历史简介》。当咱们学习 ES6 的模块化,就会发现它的发展深受 CommonJS 的影响。经过这篇文章,咱们将看到 export
和 import
语句,以及 ES6 模块是怎么与 CommonJS 模块兼容的。html
在 ES6 模块系统中,严格模式是默认开启的。严格模式是语言从语法层面限制你使用一些很差的写法,因此它更严格(==)。它也让编译器更好地处理代码。下面是 MDN上关于严格模式的解释:严格模式es6
with
00840
这样的八进制数字会报语法错误delete
没法删除的属性会报错delete prop
会报语法错误,可使用 delete global[prop]
eval
不会在所在的词法做用域引入新的变量eval
和 arguments
不能被改变或赋值arguments
不会跟踪方法的参数变化arguments.callee
再也不支持,会报 TypeError
arguments.caller
再也不支持,会报 TypeError
this
再也不被强制转换成 Object
fn.caller
和fn.arguments
再也不支持protected
,static
,interface
不能被绑定即便在 ES6 中严格默认是默认开启的,也推荐在每一个模块中都使用 use strict
关键字。api
让咱们先来看下 export
关键字吧~浏览器
export
在 CommonJS 中,导出模块能够用 module.exports
。从下面的代码能够看出,你能够导出任何值:模块化
module.exports = 1 module.export = NaN module.exports = 'foo' module.exports = { foo: 'bar' } module.exports = ['foo', 'bar'] module.exports = function foo () {}
像 CommonJS 模块同样,ES6 模块也是暴露 API 的文件。一样的,ES6 模块内部的声明只在模块内部有效。这就意味着,某个模块中的变量,若是没有被导出,在其余模块中就没法使用。函数
上面的 CommonJS 代码若是用 ES6 语法写起来也很类似,主要就是将 module.exports
替换为 export default
工具
export default 1 export default NaN export default 'foo' export default { foo: 'bar' } export default ['foo', 'bar'] export default function foo () {}
而与 CommonJS 不一样的是,export
语句只能放在 ES6 模块代码的顶层,就算放在一个当即执行的函数中也不行。可想而知,这种限制让编译器更容易解析 ES6 模块,同时也让避免了在方法中动态导出这种不是很实用的骚操做。学习
function foo () { export default 'bar' // SyntaxError } foo()
导出语法不是只有 export default
,你也可使用具名导出。ui
在 CommonJS 中导出时也不是必须将 module.exports
赋值为一个对象,你能够直接改变它的属性。this
module.exports.foo = 'bar' module.exports.baz = 'ponyfoo'
好了,下面用 ES6 的写法也很简单(原文说了一堆不过重要就没翻~):
export var foo = 'bar' export var baz = 'ponyfoo'
有一句话要始终牢记,咱们导出的是绑定而不是值。
在 ES6 模块中重要的一个点是:导出的是绑定,而不是值或者引用。这就意味着你导出的变量foo
被绑定在了模块上,它的值改变了,外部也能收到变化。尽管一般状况下不推荐在模块加载后改变导出的值。
若是你有一个 ./a
模块,导出的 foo
将在 500ms 后从 bar
变为 baz
:
export var foo = 'bar' setTimeout(() => foo = 'baz', 500)
除了默认绑定和具名绑定,咱们还能够导出一个列表的绑定。
下面的代码能够看到,ES6 模块容许咱们导出一个包含顶级成员的列表:
var foo = 'ponyfoo' var bar = 'baz' export { foo, bar }
若是你想改变其中变量的名字,能够用如下语法:
export { foo as ponyfoo }
同时也能够指定其中的某个为默认导出:
export { foo as default, bar }
而一般的最佳实践是只使用 export default
,而且将其放在模块的底部。
export
若是同时使用命名导出,导出列表和默认导出,很容易形成困扰,因此大部分状况下做者建议只使用 export default
,而且将语句放在模块文件的底部。你能够将要导出的对象叫做 api
:
var api = { foo: 'bar', baz: 'ponyfoo' } export default api
这样作有几个好处:(原文比较啰嗦,下面提炼两点)
export default
具备一致性,不会由于过多导出方式形成混淆,在使用的时候也更加方便。咱们已经熟悉了 export
的 API 和注意事项,下面来看 import
语句。
import
做为与 export
相对的语句,import
可让咱们导入另外一个模块中的内容。模块的加载方式,在浏览器端主要依靠 Babel 实现。而在内部 import
语句的实现也几乎是和 CommonJS 的 require
语句同样。
让咱们用 lodash
来讲明。下面的语句简单地加载了 Lodash 模块到咱们本身的模块,它没有建立任何变量,但它将会执行 lodash
模块顶层代码的内容。
import 'lodash'
在讲导入绑定以前,咱们须要先明确的是,跟 export
语句相似,import
语句也只能在模块的顶层代码使用。这能让编译器更好地处理解析工做,也能帮助其余静态分析工具解析咱们的代码。
在 CommonJS 中,咱们使用 require
导入内容:
var _ = require('lodash')
在 ES6 模块中,咱们只须要为导入的绑定起一个名字:
import _ from 'lodash'
咱们也能够导入具名导出的内容。
这个语法跟 ES6 的解构赋值很类似,可是也不太同样:
import { map, reduce } from 'lodash'
跟解构赋值不一样的其中一点是,你能够为导入的绑定建立别名,能够同时使用有别名和没有别名的导入。
import { cloneDeep as clone, map } from 'lodash'
也能够同时使用具名和默认的导入,若是要在花括号内使用默认导入,须要使用 default
关键字,固然也能够给它起个别名,或者像第三行那样:
import { default, map } from 'lodash' import { default as _, map } from 'lodash' import _, { map } from 'lodash'
import
All The Things咱们也能够导入一个模块中的整个命名空间。import *
这个语法后面必须跟一个别名,这个别名中就存放了导入模块的全部绑定。若是里面包含一个默认导出,那么它放被放在了 alias.default
中
import * as _ from 'lodash'
咱们今天能够直接使用 ES6 模块,得益于 Babel 编译器借助了 CommonJS 模块的实现。这其中的一个好处就是 CommonJS 和 ES6 模块之间是兼容的,即咱们能够在 ES6 模块中直接写 CommonJS 的语法。
ES6 模块系统看起来很棒,而它也是 JavaScript 中的一个最重要的功能。但愿在不久的未来,模块加载 API 能够最终敲定并直接在浏览器端实现。
欢迎关注个人公众号:码力全开(codingonfire)