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
表示从哪个模块导入给定的模块,该模块由表示模块路径的字符串指定(被称作模块说明符)。浏览器使用的路径格式与传统