ES6的模块化语法

3,572 阅读7分钟

在模块化被写入 ECMAScript 标准之前, 已经存在各种模块化的实现方式和对应的语法, 例如 AMD, CMD, commonJS 等. 本文只讨论 ES6 标准下的模块化语法.

模块化的必要性

当我们希望全局作用域更加干净,而不是到处都有命名冲突之类的问题;

当我们希望一段代码拥有自己的作用域, 而且不要被其他代码所污染;

当我们希望自己的程序更加井然有序;

当......

这也是 ES6 希望解决的问题, 所以将模块化定为了标准.

模块的概念

模块是一段JavaScript代码, 这段代码会自动运行、而且是在严格模式下、并且无法退出运行。

模块的特点

  • 模块内定义的变量不会被自动添加到全局作用域
  • 由于上面的原因, 模块要向外面暴露一些自己的数据, 这些数据可以被外界访问到
  • 一个模块可以从另外一个模块中导入数据(即可以使用其他模块的数据)
  • 模块顶层的 thisundefined, 并不是 windowglobal

模块和脚本的区别

模块和脚本初看起来很相似, 他们都可以存在一个单独的文件中, 都可以被其他模块(脚本)引入, 也可以引入其他模块(脚本)的数据, 似乎很难说出他们之间的区别.

但是其实他们的区别非常明显, 用一句话就可以概括: 我们可以按需导入模块中的数据, 但是不能按需导入脚本中的数据。

对于脚本,我们一旦导入了它, 就会将脚本中的所有数据全都导入到了全局作用域中, 这样看起来还是挺乱的。

而对于模块, 则可以只导入我们需要的数据。虽然一个模块可能会暴露出很多的变量、函数和对象, 但我们可以只把需要的那一部分导入进来使用。

从其他模块导入数据和将模块中的数据暴露出去给其他模块的语法 exportimport

将模块中的数据暴露(导出)给其他模块 export

将模块中的数据暴露给其他模块使用的语法有两种, 分别如下:

  1. 先定义一个值,然后将其暴露出去

// 定义一个变量
let name = 'doug';
// 暴露这个变量
export name;

// 定义一个函数
function say(){
    console.log('hello ' + name);
}
// 暴露这个函数
export say;

// 定义一个类
class Student {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}
// 暴露这个类
export Student;

// 定义一个函数,而且不把它暴露出去
function privateFunc(){
    console.log('我是私有成员,没有被暴露出去,所以别人没有办法访问到我');
}

上面的操作可以分为两个过程, 先定义、再暴露。其实可以简化为一句代码: 2. 在定义变量的同时暴露数据, 只要在声明符之前加上 export 就可以.

// 定义一个变量并暴露
export let name = 'doug';

// 定义一个函数并暴露
export function say(){
    console.log('hello ' + name);
}

// 定义一个类并暴露
export class Student {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}

// 定义一个函数,并不把它暴露出去, 其他模块从外部无法访问这个函数
function privateFunc(){
    console.log('我是私有成员,没有被暴露出去,所以别人没有办法访问到我');
}

注意, 不暴露出去的数据是从外部访问不到的, 例如上面例子中的 privateFunc 函数.

从别的模块导入数据 import

从别的模块得到数据以供自己使用, 要用 import 关键字。在导入之前, 要先明确两个问题: 一是从哪个模块导入? 二是想导入模块中的什么数据?

在明确从哪导入和导入什么之后,就可以按照下面的语法来写了:

import { 数据1, 数据2, ... , 数据n } from 模块名

例如,从 a 模块中导入 name 变量 和 say 函数:

import { name, say } from './a.js';

这样就把 name 和 say 从 a 模块导入进本模块中了,现在就可以使用它们了, 例如输出 name 的值, 或者调用 say 这个函数:

console.log(name);

say();

关于导入的注意事项

导入的数据是没有办法直接改变的。比如,我们无法直接给 name 重新赋值, 这样会报错。

import { name } from './a.js';

name = 'new name';  // Error 错误

但是可以间接的修改这个变量的值. 假如在模块 a 中定义了一个可以修改 name 值的函数, 则可以通过调用这个函数来修改 name 的值了. a 模块中的内容:

... 包括 name 在内的其他数据

function updateName(newName){
    name = newName;
}

导入 a模块的 updateName 函数来修改 name 的值:

import { name, updateName } from './a.js';

updateName('new Name'); // name的值已经被修改为了 "new name"

一次性导入一个模块暴露出来的所有数据

当想用一行代码导入一个模块暴露出来的所有数据时, 用上面的方法可能比较困难, 因为一个模块暴露出来的方法可能很多, 把每个变量名都写出来的方法就不够灵活了.

这个时候可以用命名空间导入的方法, 即将一个模块暴露出来的所有的数据挂载到一个对象上,通过这个对象来调用这些数据, 例如:

import * as aObj from './a.js'; // a 模块所有暴露出的数据都挂到了 aObj 上

// 通过 aObj 来访问和调用 a 模块中的变量和方法
console.log(aObj.name);
aObj.say();

使用 as 对导入和要暴露出去的数据重命名

如果想将数据以别的名称暴露出去, 可以使用 as 关键字, 语法为: export { 原名称 as 新名称 } .

这样在其他模块中就可以用这个新的名称来导入这个数据了, 例如将函数 say 以 speak 为名称暴露出去:

export {say as speak};

那么其他模块就要用 speak 来导入这个函数了: import { speak } from './a.js'

在导入一个数据时, 也可以对它重命名, 并使用重命名之后的名称来访问它, 例如将它命名为 Howling, 之后就可以使用 howling 来访问这个函数了:

import { speak as howling} from './a.js';  // 导入时重命名

howling(); // 用新名字调用这个函数

将一个值作为默认值暴露出去 default

每个模块可以将一个变量、函数或者类作为默认值暴露出去.

将一个值作为默认值暴露出去的语法有3种,分别是:

  1. 定义的时候就暴露
  2. 先定义后暴露
  3. 将这个值用 as 命名为 default 后暴露
// 1. 定义的时候就暴露
export default function(){
    console.log('方法1暴露函数为默认值');
}

// 2. 先定义后暴露
let name = 'doug';
export default name;

// 3. 将这个值用 as 命名为 default 暴露
class Student{ // ... }
export {Student as default};

导入其他模块暴露出的默认值

导入默认值的语法和导入非默认值的语法略有不同. 导入一个模块暴露出的默认值, 并不用 {} 来包裹, 而且不用使用 as 关键字就可以将这个默认值重命名: 例如导入上面例子中的 name, 并将其重命名为 familyName

import familyName from './a.js';

同时导入默认值和非默认值的语法

在一行语句中同时导入默认值和非默认值的语法非常简单, 就是将这两种导入的方式结合起来, 非默认值用{}括起来, 而默认值不用. 语法为:

import 默认值 { 非默认值 } from './a.js';

将从其他模块导入的数据暴露出去

有时候需要将从其他模块导入的某些数据再暴露出去, 这有两种语法

  1. 先导入, 再暴露
import { name } from './a.js';   // 导入

export {name};   // 暴露
  1. 一行代码之内同时导入和暴露
export { name } from './a.js';

也可以重命名后再暴露出去

export { name as lastName } from './a.js';

#完. 文章或有疏漏之处, 欢迎指正.

在模块化被写入 ECMAScript 标准之前, 已经存在各种模块化的实现方式和对应的语法, 例如 AMD, CMD, commonJS 等. 后面的文章会对这些标准进行对比.