什么是模块?

模块( Modules )是使用不同方式加载的 JS文件(与JS原先的脚本加载方式相对)。这种不同模式很有必要,因为它与脚本( script )有大大不同的语义:

  • 模块代码自动运行在严格模式下,并且没有任何办法跳出严格模式;
  • 在模块的顶级作用域创建的变量,不会被自动添加到共享的全局作用域,它们只会在模块顶级作用域的内部存在;
  • 模块顶级作用域的 this 值为 undefined ;
  • 模块不允许在代码中使用 HTML 风格的注释(这是 JS 来自于早期浏览器的历史遗留特性);
  • 对于需要让模块外部代码访问的内容,模块必须导出它们;
  • 允许模块从其他模块导入绑定。

基本的导出

你可以使用 export 关键字将已发布代码部分公开给其他模块。最简单方法就是将export放置在任意变量、函数或类声明之前,从模块中将它们公开出去:

// 导出数据
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// 导出函数
export function sum(num1, num2) {
    return num1 + num1;
}
// 导出类
export class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
}
// 此函数为模块私有
function subtract(num1, num2) {
    return num1 - num2;
}
// 定义一个函数……
function multiply(num1, num2) {
    return num1 * num2;
}
// ……稍后将其导出
export { multiply };

要求:

  • 导出的函数声明与类声明必须要有名称

基本导入

import { sum, multiply } from "./example.js";    

import后面的括号里写上需要导入到方法名,多个用','号隔开

要求:

  • 你对同一个模块使用了多少次 import 语句,该模块都只会被执行一次。

    完全导入一个模块

还有一种特殊情况,即允许你将整个模块当作单一对象进行导入,该模块的所有导出都会作为对象的属性存在。例如:

// 完全导入
import * as example from "./example.js";
console.log(example.sum(1,
example.magicNumber)); // 8
console.log(example.multiply(1, 2)); //

重命名导出与导入

你可能并不想使用从模块中导出的变量、函数或类的原始名称。假设你想用不同的名称来导出一个函数,你可以使用 as 关键字来指定新的名称,以便在模块外部用此名称指代目标函数

function sum(num1, num2) {
    return num1 + num2;
}
export { sum as add };

此处的 sum() 函数被作为 add() 导出,前者是本地名称( local name ),后者则是导出名称( exported name )。这意味着当另一个模块要导入此函数时,它必须改用 add 这个名称:

import { add } from "./example.js

导入默认值

你可以使用如下语法来从一个模块中导入默认值:

// 导入默认值
import sum from "./example.js";
console.log(sum(1, 2)); // 3

对于既导出了默认值、又导出了一个或更多非默认的绑定的模块,你可以使用单个语句来入它的所有导出绑定。例如,假设你有这么一个模块:

export let color = "red";
export default function(num1, num2) {
    return num1 + num2;
}

导入:

import sum, { color } from "./example.js";
console.log(sum(1, 2)); // 3
console.log(color); // "red"

绑定的再导出

export { sum } from "./example.js";

无绑定的导入

有些模块也许没有进行任何导出,相反只是修改全局作用域的对象。尽管这种模块的顶级变量、函数或类最终并不会自动被加入全局作用域,但这并不意味着该模块无法访问全局作用域。诸如Array与Object之类的内置对象的共享定义在模块内部是可访问的,并且对于这些对象的修改会反映到其他模块中。

例如,若你想为所有数组添加一个 pushAll() 方法,你可以像下面这样定义一个模块:

// 没有导出与导入的模块
Array.prototype.pushAll = function(items) {
    // items 必须是一个数组
    if (!Array.isArray(items)) {
        throw new TypeError("Argument must be an array.");
    }
    // 使用内置的 push() 与扩展运算符
    return this.push(...items);
};

这是一个有效的模块,尽管此处没有任何导出与导入。此代码可以作为模块或脚本来使用。由于它没有导出任何东西,你可以使用简化的导入语法来执行此模块的代码,而无须导入任何绑定:

import "./example.js";
let colors = ["red", "green", "blue"];
let items = [];
items.pushAll(colors);