这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
前言
在 ES6
之前,社区制定了一些模块加载方案,例如最主要的有 CommonJS
和 AMD
两种。前者用于服务器,后者用于浏览器。ES6
在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS
和 AMD
规范,成为浏览器和服务器通用的模块解决方案。
本篇文章主要介绍ES6
的module
语法,以及相关的提案。
模块的嵌入
带有 type="module"
属性的<script>
标签会告诉浏览器相关代码应该作为模块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中,也可以作为外部文件引入:
<script type="module">
// 模块代码
</script>
<script type="module" src="path/to/myModule.js"></script>
复制代码
具有以下特点:
- 对于多个模块标签,会按书写顺序执行。
- 引擎解析到
<script type="module">
标签后会立即下载模块文件,但执行会延迟到文档解析完成。 - 同一个模块只会被加载一次。
<!-- 第二个执行 -->
<script type="module"></script>
<!-- 第三个执行 -->
<script type="module"></script>
<!-- 第一个执行 -->
<script></script>
//以模块的方式加载外部文件
<!-- 第二个执行 -->
<script type="module" src="module.js"></script>
<!-- 第三个执行 -->
<script type="module" src="module.js"></script>
<!-- 第一个执行 -->
<script>复制代码
导出
模块导出使用export
关键字。
导出语句只能在模块的最外层使用。
// 允许
export ...
// 不允许
if (condition) {
export ...
}
复制代码
命名导出
命名导出要求导出的变量必须已经声明,不能是默认变量。
以命名导出的模块其实是一个Module
的对象,导出的变量时这个对象的值。
Module {__esModule: true, Symbol(Symbol.toStringTag): "Module"}
Line: Object
foo: "bar"
Symbol(Symbol.toStringTag): "Module"
__esModule: true
get Line: ƒ ()
get foo: ƒ ()
__proto__: Object
复制代码
逐个导出
逐个导出是单独对每个要导出的变量进行export
操作,但是如果变量声明跟导出不在一行,则导出是要将导出值包含在一个括号内。
// 允许
const foo = 'foo';
export { foo };
// 允许
export const foo = 'foo';
// 允许,但应该避免
export { foo };
const foo = 'foo';
//错误
export foo
复制代码
集体导出
先逐个声明变量,最后再进行导出声明。
//逐个导出
export const foo = 'foo';
export const bar = 'bar';
export const baz = 'baz';
//集体导出
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
export { foo, bar, baz };
复制代码
起别名
导出时也可以提供别名,别名必须在export
子句的大括号语法中指定。
const foo = 'foo';
export { foo as myFoo };
const foo = 'foo';
const bar = 'bar';
const baz = 'baz';
export { foo, bar as myBar, baz };
复制代码
因此在导出的模块中,bar
会以myBar
这个属性名存在。
默认导出
命名导出的属性都回作为Module
对象的属性值存在,外部导入时必须要使用对象解析运算符来引用模块的值。
默认导出可以直接导出一个模板的变量,而不用包装在Module
对象内。
具有以下特点:
- 每个模块只能有一个默认导出, 重复的默认导出会导致
SyntaxError
。 - 默认导出的属性值就是这个属性在模块内的原始值。
- 默认导出的属性在被导入是会被视为一个匿名属性,因此可以直接给它起别名。
- 由于默认导出的值会被视为一个匿名值,因此导出的属性可以不用声明。
常见导出形式
const foo = 'foo';
export default foo;
const foo = 'foo';
// 等同于 export default foo;
export { foo as default };
//不声明导出
export default 'foo';
export default 123;
export default /[a-z]*/;
export default { foo: 'foo' };
//导出匿名变量
export default function() {}
export default function foo() {}
export default function*() {}
export default class {}
复制代码
错误形式
// 行内默认导出中不能出现变量声明
export default const foo = 'bar';
// 只有标识符可以出现在 export 子句中,123是常量
export { 123 as default }
// 别名必须在 export 子句的大括号语法中指定
export const foo = 'foo' as default;
复制代码
导入
与export
一样,import
只能在最外层使用。
相关用法:
import { foo } from './fooModule.js';
//foo是默认导出,剩下的是命名导出
import foo, * as Foo './foo.js';
复制代码
注意事项:
-
导入的模块是只读的,相当于用
const
声明的变量。 -
在使用*执行批量导入时,赋值给别名的命名导出就好像使用
Object.freeze()
冻结过一样。 -
如果导入的属性的值是个基本数据类型,那么直接修改它会报
not defined
的错误。但可以修改导出对象的属性。
//foo.js
export default {name:"foo.js"}
export const foo = "foo.js"
//bar.js
import foo, * as Foo './foo.js'; //分别导入模块的默认导出和命名导出,并所有命名导出起别名归为一个Foo的Module对象
foo = 'foo'; // 错误,直接修改导出的值
Foo.foo = 'foo'; // 错误,修改导出的Module对象的属性
delete Foo.foo // 错误,修改导出的Module对象的属性
foo.bar = 'bar'; // 允许,可以修改导出对象的属性
复制代码
命名导出和默认导出的区别也反映在它们的导入上。命名导出可以使用*
批量获取并赋值给保存导出集合的别名,而无须列出每个标识符:
const foo = 'foo', bar = 'bar', baz = 'baz';
export { foo, bar, baz } //导出
import * as Foo from './foo.js'; //导入
console.log(Foo.foo); // foo
console.log(Foo.bar); // bar
console.log(Foo.baz); // baz
复制代码
起别名
要给导入的属性起别名,需要在import
语句的大括号中使用as
关键字:
import { foo, bar, baz as myBaz } from './foo.js';
console.log(foo); // foo
console.log(bar); // bar
console.log(myBaz); // baz
复制代码
默认导入与命名导入
默认导出就好像整个模块就是导出的值一样。可以使用default
关键字并给其提供别名来导入。也可以不使用大括号,此时指定的标识符就是默认导出的别名:
// 第一句等效于第二句
import { default as foo } from './foo.js';
import foo from './foo.js';
复制代码
如果模块同时导出了命名导出和默认导出,则可以在import
语句中同时取得它们。可以依次列出特定导出的标识符来取得,也可以使用*
来取得:
import foo, { bar, baz } from './foo.js';
import { default as foo, bar, baz } from './foo.js';
import foo, * as Foo from './foo.js';
复制代码
仅加载模块
如果不需要模块的导出,仅仅需要加载和执行模块以使用其运行结果,可以只通过路径加载它:
import './foo.js';
复制代码
转移导出
语法上相当于export
与 import
的复合写法,在一个模块内导入另一个模块后,可以同时将其作为该模块的属性导出。
//把一个模块的所有命名导出集中在一块,通过*导出
export * from './foo.js';
复制代码
可以通过这种方式,将一个模块的默认导出转换为命名导出,或者相反。
将默认导出变成命名导出
//foo.js
export default 'foo';
//bar.js
export {default as bar} from './foo.js'
复制代码
将命名导出变成默认导出
//foo.js
export const foo = 'foo';
//bar.js
export { foo as default} from './foo.js'
复制代码
将默认导出重用为当前模块的默认导出
//foo.js
export default 'foo';
//bar.js
export { default } from './foo.js';
复制代码
TC39:export default from
TC39:export default from目前处于stage1
,提议说的是一种es6没有提供的模块导出方式,因为转移导出语法与import语法非常类似,几乎每种import
语法都有相对应的转移导出语法,但是漏了一种:
export someIdentifier from "someModule";
export someIdentifier, { namedIdentifier } from "someModule";
//对应于
import someIdentifier from "someModule";
import someIdentifier, { namedIdentifier } from "someModule";
复制代码
这种语法的作用是导入一个模块的默认导出,并将其转换为本模块的命名导出。
export someIdentifier from "someModule";
//等价于
import someIdentifier from "someModule";
export {someIdentifier}
复制代码
解决方法是使用babel
插件@babel/plugin-proposal-export-default-from
异步加载
ES2020提案 引入import()
函数,支持动态加载模块。
import(modulePath)
复制代码
与import
、export
作为关键字不同,import()
是一个函数,其接收一个标识符参数,因此这个标识符是动态的,可拼接的。
const name = 'chart'
import(`@/components/${name}`)
.then(module=>{
//TODO
})
复制代码
import()
返回一个 Promise
对象,模块加载成功以后,这个模块会作为then
方法的参数。
总结
导入语法
表达式 | 导入模块 | 导出模块 | 本地名称 |
---|---|---|---|
import v from "mod"; |
"mod" |
"default" |
"v" |
import * as ns from "mod"; |
"mod" |
"*" |
"ns" |
import {x} from "mod"; |
"mod" |
"x" |
"x" |
import {x as v} from "mod"; |
"mod" |
"x" |
"v" |
import "mod"; |
|||
import() |
导出语法
表达式 | 导入模块 | 导出模块 | 本地名称 | 导出名称 |
---|---|---|---|---|
export var v; |
null | null | "v" |
"v" |
export default function f(){}; |
null | null | "f" |
"default" |
export default function(){}; |
null | null | "*default*" |
"default" |
export default 42; |
null | null | "*default*" |
"default" |
export {x} ; |
null | null | "x" |
"x" |
export {x as v} ; |
null | null | "x" |
"v" |
export {x} from "mod" ; |
"mod" |
"x" |
null | "x" |
export {x as v} from "mod" ; |
"mod" |
"x" |
null | "v" |
export * from "mod" ; |
"mod" |
"*" |
null | null |
新增提议
表达式 | 导入模块 | 导出模块 | 本地名称 | 导出名称 |
---|---|---|---|---|
export v from "mod"; |
"mod" |
"default" |
null | "v" |