使用ECMAScript 6 模块封装代码

505 阅读23分钟

JavaScript 用“共享一切”的方法加载代码,这是该语言中最容易出错且最容易让人感到困惑的地方。其他语言使用诸如包这样的概念来定义代码作用域,但在 ECMAScript 6 以前,在应用程序的每一个 JavaScript 中定义的一切都共享一个全局作用域。随着 Web 应用程序变得更加复杂,JavaScript 代码的使用量也开始增长,这样会引起问题,如命名冲突和安全问题。ECMAScript 6 的一个目标就是解决作用域问题,也为了使 JavaScript 应用程序显得有序,于是引进了模块。

什么是模块

模块是自动运行在严格模式下并且没有办法退出运行的 JavaScript 代码。与共享一切架构相反的是,在模块顶部创建的变量不会不会被自动添加到全局共享作用域,这个变量仅在模块的顶级作用域中存在,而且模块必须导出一些外部代码可以访问的元素,如变量或函数。模块也可以从其他模块导入绑定。

另外两个模块的特性与作用域关系不大,但也很重要。首先,在模块的顶部, this 的值是 undefined ;其次,模块不支持 HTML 风格的代码注释,这是从早期浏览器残留下来的 JavaScript 特性。

脚本,也就是任何不是模块的 JavaScript 代码,则缺少这些特性。模块和其他 JavaScript 代码之间的差异可能乍一看不起眼,但是它们代表了 JavaScript 代码加载和求值的一个重要变化。模块真正的魔力所在是仅导出和导入你需要的绑定,而不是将所有东西都到一个文件。只有很好地理解了导出和导入才能理解模块与脚本的区别。

导出的基本语法

可以用 export 关键字将一部分已发布的代码暴露给其他模块,在最简单的用例中,可以将 export 放在任何变量、函数或类声明的前面,以将它们从模块导出,像这样:

//导出数据
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;

//导出函数
export function sum(num1,num2){
    return num1 + num2;
}

//导出类
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;

在这个示例中需要注意几个细节,除了 export 关键字外,每一个声明与脚本中的一模一样。因为导出的函数和类声明需要有一个名称,所以代码中的每一个函数或类也确实有这个名称。除非用 default 关键字,否则不能用这个语法导出匿名函数或类(随后在“模块的默认值”中会详细讲解)。

另外,我们看 multiply() 函数,在定义它时没有马上导出它。由于不必总是导出声明,可以导出引用,因此这段代码可以运行。此外,请注意,这个示例并未导出 subtract() 函数,任何未显示导出的变量、函数或类都是模块私有的,无法从模块外部访问。

导入的基本语法

从模块中导出的功能可以通过 import 关键字在另一个模块中访问, import 语句的两个部分分别是: 要导入的标识符和标识符应当从哪个模块导入。

import { identifier1, identifier2 } from "./example.js";

import 后面的大括号表示从给定模块导入的绑定,关键字 from 表示从哪个模块导入给定的模块,该模块由表示模块路径的字符串指定(被称作模块说明符)。浏览器使用的路径格式与传统