ES-Module快速入门

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

前言

ES6 之前,社区制定了一些模块加载方案,例如最主要的有 CommonJSAMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJSAMD 规范,成为浏览器和服务器通用的模块解决方案。

本篇文章主要介绍ES6module语法,以及相关的提案。

模块的嵌入

带有 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><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';
复制代码

转移导出

语法上相当于exportimport 的复合写法,在一个模块内导入另一个模块后,可以同时将其作为该模块的属性导出。

//把一个模块的所有命名导出集中在一块,通过*导出
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)
复制代码

importexport作为关键字不同,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"
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享