2.2 ES6 Module

JavaScript之父Brendan Eich在最初设计这门语言时,并没有包含模块的概念。基于越来越多的工程需求,为了使用模块化进行开发,JavaScript社区涌现出了多种模块标准,其中也包括CommonJS。一直到2015年6月,由TC39标准委员会正式发布了ES6(ECMAScript 6.0),自此JavaScript语言才具备了模块这一特性。

2.2.1 模块

请看下面的例子,我们使用ES6的方式改写前面的calculator.js和index.js。

// calculator.js
export default {
    name: 'calculator',
    add: function(a, b) {
        return a + b;
    }
};
// index.js
import calculator from './calculator.js';
const sum = calculator.add(2, 3);
console.log(sum); // 5

ES6 Module也是将每个文件作为一个模块,每个模块拥有自身的作用域,不同的是导入、导出语句。ES6版本将import和export作为保留关键字加入了进来(CommonJS中的module并不属于关键字)。

ES6 Module会自动采用严格模式,该模式在ES5(ECMAScript 5.0)中只是一个可选项。也就是说,以前我们可以通过选择是否在文件开始时加上use strict来控制严格模式,而在ES6 Module中不管开头是否有use strict都会采用严格模式。所以,在将原本是CommonJS的模块或任何未开启严格模式的代码改写为ES6 Module时要注意这一点。

2.2.2 导出

在ES6 Module中使用export命令来导出模块。export有两种形式:

·命名导出

·默认导出

一个模块可以有多个命名导出。它有两种不同的写法:

// 写法1
export const name = 'calculator';
export const add = function(a, b) { return a + b; };
// 写法2
const name = 'calculator';
const add = function(a, b) { return a + b; };
export { name, add };

第1种写法是将变量的声明和导出写在一行;第2种则是先进行变量声明,再用同一个export语句导出。两种写法的效果是一样的。

在使用命名导出时,可以通过as关键字对变量重命名。如:

const name = 'calculator';
const add = function(a, b) { return a + b; };
export { name, add as getSum }; // 在导入时即为 name 和 getSum

与命名导出不同,模块的默认导出只能有一个。如:

export default {
    name: 'calculator',
    add: function(a, b) {
        return a + b;
    }
};

我们可以将export default理解为对外输出了一个名为default的变量,因此不需要像命名导出一样进行变量声明,直接导出值即可。

// 导出字符串
export default 'This is calculator.js';
// 导出 class
export default class {...}
// 导出匿名函数
export default function() {...}

2.2.3 导入

ES6 Module中使用import语法导入模块。首先我们来看如何加载带有命名导出的模块,请看下面的例子:

// calculator.js
const name = 'calculator';
const add = function(a, b) { return a + b; };
export { name, add };
   
// index.js
import { name, add } from './calculator.js';
add(2, 3);

加载带有命名导出的模块时,import后面要跟一对大括号来将导入的变量名包裹起来,并且这些变量名应该与导出的变量名完全一致。导入变量的效果相当于在当前作用域下声明了这些变量(name和add),并且不可对其进行更改,也就是所有导入的变量都是只读的。

与命名导出类似,我们可以通过as关键字对导入的变量重命名。如:

import { name, add as calculateSum } from './calculator.js';
calculateSum(2, 3);

在导入多个变量时,我们还可以采用整体导入的方式。如:

import * as calculator from './calculator.js';
console.log(calculator.add(2, 3));
console.log(calculator.name);

使用import * as <myModule>可以把所有导入的变量作为属性值添加到<myModule>对象中,从而减少对当前作用域的影响。

接下来处理默认导出,请看下面这个例子:

// calculator.js
export default {
    name: 'calculator',
    add: function(a, b) { return a + b; }
};
   
// index.js
import myCalculator from './calculator.js';
calculator.add(2, 3);

对于默认导出来说,import后面直接跟变量名,并且这个名字可以自由指定(比如这里是myCalculator),它指代了calculator.js中默认导出的值。从原理上可以这样去理解:

import { default as myCalculator } from './calculator.js';

最后看一个两种导入方式混合起来的例子:

// index.js
import React, { Component } from 'react';

这里的React对应的是该模块的默认导出,而Component则是其命名导出中的一个变量。

注意

这里的React必须写在大括号前面,不能颠倒顺序,否则会提示语法错误。

2.2.4 复合写法

在工程中,有时需要把某一个模块导入之后立即导出,比如专门用来集合所有页面或组件的入口文件。此时可以采用复合写法:

export { name, add } from './calculator.js';

复合写法目前只支持被导入模块(这里的calculator.js)通过命名导出的方式暴露出来的变量,默认导出则没有对应的复合形式,只能将导入和导出拆开写。

import calculator from "./calculator.js ";
export default calculator;