◆

ES6 export 命令导出模块接口用法

2023-02-02 01:370

export 命令

模块功能主要由两个命令构成:export 和 import。

  • export 命令用于规定模块的对外接口,
  • import 命令用于输入其他模块提供的功能。

在 ES6 之前,JS 没有官方的模块方案,我们只能靠 IIFE 把变量藏进函数作用域,或者用 CommonJS、AMD 这类社区规范凑。ES6 把模块写进了语言层面:一个文件就是一个模块,文件内部的顶层变量默认全是私有的,外面拿不到;只有被 export 显式标出来的东西,才构成这个模块的对外接口。

这套设计的关键在于它是静态的。export 和 import 的关系在代码解析阶段就能确定,不需要执行代码。正因为如此,打包工具才有办法做 tree shaking,把没有被 import 的导出直接从产物里删掉;编辑器也才能给出准确的跳转和自动补全。而 CommonJS 的 require 是运行时才执行的函数调用,路径可以是变量拼出来的,这些静态分析就无从下手。

示例

export 有两种写法:一是直接写在声明前面(export const a = 1),二是先声明、末尾再用大括号统一列出接口。我更偏向第二种,因为把所有对外接口集中在文件底部一眼能看全,不用满文件找 export 关键字。

下面这段 HTML 通过 <script type="module"> 引入模块。注意 type="module" 是必须的,普通 <script> 里写 import 会直接报语法错误。另外模块脚本受同源策略约束,用 file:// 直接打开本地文件会被 CORS 拦掉,需要起一个本地服务才能跑通。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <script type="module">
      import { test, test2s, test2 as test2ss, test2 } from './export.js';
      // 触发 export.js 的 test 事件
      test();
      // 输出三个 这是 export.js 的 test2 变量
      console.log(test2s, test2ss, test2);
    </script>
  </body>
</html>

被引入的 export.js 文件如下。这里演示了 as 重命名:同一个 test2 变量既以原名导出,又用 test2 as test2s 起了个别名导出。所以外部既能 import { test2 },也能 import { test2s },拿到的是同一份值——这也是为什么上面那段代码打印出三个相同的字符串。

function test() {
  console.log('触发 export.js 的 test 事件');
}

const test2 = '这是 export.js 的 test2 变量';

export { test, test2, test2 as test2s };

有一点和 CommonJS 差别很大,值得单独强调:export 导出的不是值的拷贝,而是变量的绑定。如果模块内部之后又修改了这个变量,引用方读到的是修改后的新值。CommonJS 的 module.exports 则是把值拷贝出去,之后模块内怎么改都影响不到已经 require 过的一方。

反过来,引用方拿到的绑定是只读的,直接给导入的变量赋值会报错:

import { test2 } from './export.js';

test2 = 'xxx'; // TypeError: Assignment to constant variable

tip

export 命令可以出现在模块的任何位置,只要处于模块顶层就可以。 如果处于块级作用域内,就会报错。

下面这段就是典型的错误写法。export default 被塞进了函数体里,属于块级作用域,直接是语法错误——注意这个报错发生在解析阶段,整个文件一行都不会执行,不是等调用 foo() 时才抛出。

function foo() {
  export default 'bar'; // SyntaxError
}

foo();

这条限制正是「静态」二字的必然结果。既然模块接口要在不执行代码的前提下就分析出来,那它就不能依赖任何运行时条件,也就不能写在 if、for、函数体这类只有跑起来才知道走不走到的位置。

如果确实需要按条件加载模块,得用动态 import()。它是一个返回 Promise 的函数形式,可以写在任意位置、路径也可以是变量拼出来的:

if (needChart) {
  const { renderChart } = await import('./chart.js');
  renderChart();
}

路由懒加载、大体积第三方库延后加载,靠的都是这个能力。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEES6 export 命令导出模块接口用法加载中