16.3 ES6 模块初窥
有两种导出的方式:命名导出(每个模块可以导出若干个)和默认导出(每个模块只能导出一个)。
16.3.1 命名导出(每个模块可以导出若干个)
通过在声明的前面加上 export
关键字,一个模块可以导出多个内容。这些导出的内容通过名字区分,被称为命名导出。
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
还有其它方式指定命名导出(后面讲解),但是我发现这种方式非常方便:尽情书写代码,就像没有外部的世界一样,然后给想要导出的所有内容加上一个关键字标签。
也可以引入整个模块,然后通过对象属性的方式来访问命名导出的内容:
//------ main.js ------
import * as lib from 'lib';
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5
在 CommonJS 中有相似的语法: 有段时间,在 Node.js 中,我尝试几种看起来很聪明的方法去处理模块导出,以便少写一些多余的代码。现在我倾向于下面这种简单的但是稍微啰嗦的风格,这不禁让人联想到文章《 revealing module pattern 》:
//------ lib.js ------
var sqrt = Math.sqrt;
function square(x) {
return x * x;
}
function diag(x, y) {
return sqrt(square(x) + square(y));
}
module.exports = {
sqrt: sqrt,
square: square,
diag: diag,
};
//------ main.js ------
var square = require('lib').square;
var diag = require('lib').diag;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
16.3.2 默认导出(每个模块只能导出一个)
仅导出单一值的模块在 Node.js 社区中非常流行。但在前端开发中也很常见,经常为 model 写一些构造器或者类,每个模块一个 model 。一个 ECMAScript 6 模块可以只有一个默认导出。默认导出的内容特别方便引入。
下面的 ECMAScript 6 模块“是”一个单一的函数:
//------ myFunc.js ------
export default function () { ··· } // no semicolon!
//------ main1.js ------
import myFunc from 'myFunc';
myFunc();
默认值是类的 ECMAScript 6 模块看起来像下面这样:
//------ MyClass.js ------
export default class { ··· } // no semicolon!
//------ main2.js ------
import MyClass from 'MyClass';
let inst = new MyClass();
16.3.2.1 export default
的操作数:声明或表达式
在语法上, export default
的操作数是:
- 一个声明:如果以函数或类开始。这意味着在前面的代码示例中,是匿名声明(仅能用于此种特殊情形)。
- 一个表达式:所有其余场景。
因此,有两种可以默认导出匿名函数的方法:
export default function () {} // function declaration
export default (function () {}); // function expression
也可以给函数声明一个名字。如果想在模块的其它位置访问默认导出的内容,这会很有用:
bar();
export default function bar() { ··· }
这段代码等价于:
bar();
function bar() { ··· }
export default bar;
16.3.3 模块导出的是不变的绑定,而不是值
相对于 AMD 模块和 CommonJS 模块, ES6 模块并不导出值(值的快照),而是绑定(指向值存储位置的引用)。这些绑定是不可变的:只能通过绑定读取值,不能修改值。但是可以从模块内部修改。下面的代码说明了这是如何运作的:
//------ lib.js ------
export let mutableValue = 3;
export let incMutableValue() {
mutableValue++;
}
//------ main1.js ------
import { mutableValue, incMutableValue } from './lib';
// The imported value is live
console.log(mutableValue); // 3
incMutableValue();
console.log(mutableValue); // 4
// The imported value can’t be changed
mutableValue++; // TypeError
如果通过星号(*)引入,会得到类似的结果:
//------ main2.js ------
import * as lib from './lib';
// The imported value is live
console.log(lib.mutableValue); // 3
lib.incMutableValue();
console.log(lib.mutableValue); // 4
// The imported value can’t be changed
lib.mutableValue++; // TypeError
16.3.4 引入的内容会提升
模块引入会提升(在内部处理中,会将引入移到当前作用域的最前面)。因此,在一个模块中,何处引入是没有关系的,下面的代码也并没有什么问题:
foo();
import { foo } from 'my_module';
16.3.5 模块文件就是普通的 JavaScript 文件
下面所有种类的文件都有相同的文件扩展名 .js
:
- 1、 ECMAScript 6 模块
- 2、 CommonJS 模块
- 3、 AMD 模块
- 4、 脚本文件(在 HTML 文件中通过
<script src="file.js">
加载)
也就是说,所列出的所有文件都是“ JavaScript 文件”。它们被如何解析,依赖于所在的使用环境。