ES6功能扩展-模块
在ES6之前,应用程序中的每一个js文件都共享一个全局作用域,随着应用程序越来越复杂,这样会很容易造成命名冲突和安全问题。为了解决作用域的问题,使其显得更有序,ES6引入了模块。
概念
模块是自动运行在严格模式下并且没有办法退出运行的JS代码。在模块顶部创建的变量不会自动添加到全局作用域,它只存在于模块的顶级作用域;
export
export关键字可以把已发布的代码暴露给其他模块,比如导出变量、函数、类声明
// example.js
// 导出数据
export let name = 'hello';
export const sex = 'boy';
// 导出函数
export function sum(num1, num2) {
return num1 + num2;
}
// 导出类
export class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// 私有函数
function subtract(num1, num2) {
return num1 - num2;
}
// 先定义,再导出
let multiply = function(num1, num2) {
return num1 * num2;
}
export {multiply}
示例描述了如何导出模块,除了export关键字外,每一个声明都与脚本中的一模一样。
示例并未导出subtract()函数,任何未显式导出的变量、函数或类都是模块私有的,无法从模块外部访问
import
从模块中导出的功能可以通过import关键字导入到另一个模块,并在另一个模块中访问,import语句的两个部分分别是要导入的标识符和应当从哪个模块导入
导入语句的基础形式
import { identifier1, identifier2 } from './example.js';
import后面的大括号表示从给定模块导入的绑定(binding),关键字from表示从哪个模块导入给定的绑定。浏览器使用的路径格式与传给<script
元素的路径格式相同,所以必须把文件扩展名也加上
当从模块中导入一个绑定时,不能用同名变量覆盖标识符,也不能在import语句前使用标识符或改变绑定的值
导入单个绑定
把前面的示例代码命名为example.js
// 只导入sum函数这一个绑定
import { sum } from './example.js';
console.log(sum(1, 2)); // 3
为了最好地兼容多个浏览器和Node.js环境,一定要在字符串之前包含/、./或../来表示要导入的文件
导入多个绑定
import {name, sum, multiply} from './example.js';
console.log(name); // hello
console.log(sum(1,2)); // 3
console.log(multiply(1,2)); // 2
导入全部绑定
可以通过*号把整个模块作为一个单一的对象导入,所有的导出都可以作为对象的属性使用
import * as example from './example.js';
console.log(example.name); // hello
console.log(example.multiply(1,2)); // 2
示例中,从example.js导出的所有绑定会被加载到example对象中,它们都可以作为example的属性被访问,这种导入格式被称作命名空间导入。
无论在import语句中把同一个模块写了多少次,该模块将只执行一次。导入模块的代码执行后,实例化过的模块被保存在内存中,只要另一个import语句引用它就可以重复使用它
import { sum } from './example.js';
import { multiply } from './example.js';
import { name } from './example.js';
实例中有3个import语句,但example加载只执行一次。
重命名
从模块导入变量、函数或类时,如果不想使用它们原始的名字,可以用as关键字进行重命名
function sum(num1, num2) {
return num1 + num2;
}
// 导出重命名
export {sum as add}
// 导入也可以重命名
import {add as sum} from './example.js';
console.log(sum(1,2)); // 3
默认值
模块的默认值指的是通过default关键字指定的单个变量、函数或类,每个模块只能设置一个默认的导出值,导出时多次使用default关键字是一个语法错误
导出默认值
export default function(num1, num2) {
return num1 + num2;
}
function sum(num1, num2) {
return num1 + num2;
}
export default sum;
示例导出了一个函数作为它的默认值
也可以通过重命名来导出默认值
function sum(num1, num2) {
return num1 + num2;
}
export { sum as default };
default是JS中的默认关键字,因此不能将其用于变量、函数或类的名称;但是,可以将其用作属性名称。所以可以用default来重命名模块
导入默认值
import sum from './example.js';
这里的sum没有大括号包裹,说明sum只是一个名称,你也可以使用其他标识符表示名称。
默认导出和非默认导出是可以一起使用的
export let name = 'hello';
export default function(num1, num2) {
return num1 + num2;
}
导入时只需要用逗号将默认的本地名称与大括号包裹的非默认值分隔开即可。需要注意的是默认值必须排在非默认值之前
import sum, {name} from './example.js';
与导出默认值一样,导入默认值时也可以使用重命名语法
import {default as sum, name} from './example.js';
console.log(sum(1, 2)); // 3
console.log(name); // hello
静态加载
ES6中的模块与node.js中的模块加载不同,nodeJS中的require语句是运行时加载,而ES6中的import是静态加载,所以有一些语法限制
1、不能使用表达式和变量等这些只有在运行时才能得到结果的语法结构
// 错误
let path = './' + 'example.js';
import {sum} from path;
2、import和export命令只能在模块的顶层,不能在代码块之中
// 错误
if(flag) {
import example from './example.js'
} else {
import example from './example2.js'
}
// 错误
if (flag) {
export default example;
} else {
export default example2;
}
上面的写法都会导致错误,因为在静态分析阶段,这些语法都是没法得到值的,这就导致不能实现按需加载。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能
let path = './' + filename;
const myModule = require(path);
这个示例就是动态加载,要加载的模块需要运行时才能知道,import无法做到这点
重新导出
如果需要重新导出已经导入的模块,可以这样做
export {sum} from './example.js';
重新导出时也可以对模块进行重命名
export {sum as add} from './example.js';
可以使用*
导出另一个模块中的所有值
export * from './example.js';
无绑定导入
某些模块可能没有任何导出或导入的操作,它也是一个有效的模块。
// 为Array对象添加pushAll方法
Array.prototype.pushAll = function(items) {
if (!Array.isArray(items)) {
throw new TypeError("Argument must be an array.");
}
return this.push(...items);
}
这段代码既可以用作模块也可以用作脚本。由于它不导出任何东西,因而可以使用简化的导入操作来执行模块代码,而且不导入任何的绑定
import './example.js';
let colors = ["red", "green", "blue"],items = [];
items.pushAll(colors);
加载模块
ES6定义了模块的语法,但它并没有定义如何加载这些模块。语法规定将加载机制抽象到一个未定义的内部方法HostResolveImportedModule中。Web浏览器和Node.js开发者可以通过对各自环境的认知来决定如何实现HostResolveImportedModule
<script>中使用模块
<script>
标签的默认行为是把JS文件作为脚本加载,即type="text/javascript"
。将type设置为"module"可以让浏览器将所有内联代码或包含在src指定的文件中的代码按照模块的方式加载
对于无法识别module值的浏览器,将自动忽略