模块入门


JavaScript 在模块的发展上,有着一段混乱的历史,正如〈命名空间管理〉中看到的,现在有 CommonJS、AMD 等模块标准,然而,亦存在着各式的变体,这使得不同标准之间的模块若要互相合作,存在着一定的困难度。

ES6 纳入了模块规范,就在于试图解决这类的问题,正如同 ES6 纳入类语法,在可以用它解决需求的情况下,应该采用以增加互通性,面对 ES6 的模块方案也是如此,在可以用它解决模块需求的情况下,当然是尽可能使用。

然而,在前端或后端,对于 ES6 模块的支持相对来说,是比较慢的,以 Node.js 来说,目前处于实验阶段,在 8.5 之后,必须使用--experimental-modules打开才能使用。

对于 ES6 模块来说,一个 .js 是一个模块文件,然而由于 ES6 模块与原本 Node.js 的模块有在加载机制与静态分析上并不相同,Node.js 必须有方式可以区分,这是 Node.js 既有的模块,或者是 ES6 的模块,因而对于 ES6 模块,暂时使用了个 .mjs 副文件名作为区别。

在浏览器上,要加载 ES6 模块,同样是透过<script>标签,然而type属性的值是"module",这让浏览器知道这会是个 ES6 模块,这之后在谈到浏览器上的 JavaScript 操作时会再说明。

总之,一个 .js 文件是一个 ES6 模块,在当中所有的名称,作用范围都局促在 .js 之中,想要可以被使用的名称,可以使用export来公开,例如,定义一个 math.js 作为模块:

function max(a, b) {
    return a > b ? a : b;
}

function min(a, b) {
    return a < b ? a : b;
}

function sum(...numbers) {
    return numbers.reduce((acc, value) => acc + value);
}

const PI = 3.141592653589793;
const E = 2.718281828459045;

let foo = 'foo';

export {max, min, sum, PI, E};

就这个math模块来说,将来其他模块可以使用的,是maxminsumPIE这些名称,foo名称没有export,它仅在math中可用,是math模块的私有变量。

如果打算在另一个模块中使用math模块,可以使用import from,就上面的模块定义来说,你必须知道export的名称是什么,然后指定import哪些名称:

import {max, sum, PI} from './math';

console.log(max(10, 5));          // 10
console.log(sum(1, 2, 3, 4, 5));  // 15
console.log(PI);                  // 3.141592653589793

虽然math模块中export了五个名称,然而,只有被import至目前模块的名称才能使用,若必要,也可以为被import的名称取个别名:

import {max as maximum} from './math';

ES6 希望只有真正需要的名称,才import至目前的模块成为该模块中的名称,如果想一次从模块中importexport的全部名称,必须有个前置名称参考至一个对象,而被import的名称,都会是该对象上的特性:

import * as math from './math';

console.log(math.max(10, 5));
console.log(math.sum(1, 2, 3, 4, 5));
console.log(math.PI);

一个模块也可以在定义名称时,同时进行export

export function max(a, b) {
    return a > b ? a : b;
}

export function min(a, b) {
    return a < b ? a : b;
}

export function sum(...numbers) {
    return numbers.reduce((acc, value) => acc + value);
}

export const PI = 3.141592653589793;
export const E = 2.718281828459045;

export时,也可以为名称取别名再export

function max(a, b) {
    return a > b ? a : b;
}

function min(a, b) {
    return a < b ? a : b;
}

function sum(...numbers) {
    return numbers.reduce((acc, value) => acc + value);
}

const PI = 3.141592653589793;
const E = 2.718281828459045;

export {max as maximum, min as minimum, sum, PI, E};

import的名称,无论是否定义为const,都是不可变动(Immutable),试图重新指定值给它,会引发TypeError

import {maximum, minimum} from './math';

maximum = function() {};  // TypeError: Assignment to constant variable.

ES6 的模块是静态的,importexport必须是在模块的顶层,也就是说,你不能在if..else或者是函数中放importexport,因为静态分析时并不执行代码。


展开阅读全文