🧳 [ECMAScript6]24.Module
概述
历史上Javascript一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。这对开发大型的、复杂的项目形成了巨大障碍。
在ES6 之前社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;好处:
严格模式
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
Export、
export default function() {...} // 默认导出
function add(x, y) {
return x * y;
}
export {add as default}; // 等同于 export defaultImport
import { lastName as surname } from './profile.js'; // 为输入的变量重新取一个名字
import * from './profile.js' // 加载整体模块
import _, { each, forEach } from 'lodash'; // 同时输入默认方法和其他接口注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。
foo();
import { foo } from 'my_module';import命令是编译阶段执行的,在代码运行之前。 由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}Export import 复合写法
如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
/*---- 导出默认接口 ---- */
export { default } from 'foo';
/*---- 具名接口改为默认接口的写法如下。 ---- */
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;import()
import 是编译时加载,这样固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。 ES2020提案 引入import()函数,支持动态加载模块。
const main = document.querySelector('main');
// 返回的是一个promise对象
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node.js 的require()方法,区别主要是前者是异步加载,后者是同步加载。
const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});适用场景
button.addEventListener('click', event => {
// 只有用户点击了按钮,才会加载这个模块。
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}import(f())
.then(...);import.meta
开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。ES2020 为 import 命令添加了一个元属性import.meta,返回当前模块的元信息。
import.meta只能在模块内部使用,如果在模块外部使用会报错。
import.meta.url返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是https://foo.com/main.js,import.meta.url就返回这个路径。如果模块里面还有一个数据文件data.txt,那么就可以用下面的代码,获取这个数据文件的路径。 2. import.meta.scriptElement
import.meta.scriptElement是浏览器特有的元属性,返回加载模块的那个<script>元素,相当于document.currentScript属性。
// HTML 代码为
// <script type="module" src="my-module.js" data-foo="abc"></script>
// my-module.js 内部执行下面的代码
import.meta.scriptElement.dataset.foo
// "abc"