概述
在ES6之前,javascript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小程序。
不过在此之前社区,社区制定了一些模块加载方案,最主要的是CommonJS和AMD两种,CommonJs用于服务器, AMD用于浏览器。
在ES6中,实现了模块功能,而且实现的也很简单,完全可以取代CommonJs和AMD,成为浏览器和服务器通用的模块解决方案。
设计思想:尽量静态化,使其在编译时就能确定模块的依赖关系,以及输出和输入变量。
CommonJs和AMD模块都只能在运行时确定这些东西。输入时必须查找对象的属性。
例如:
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
这里实质是整体加载fs模块,生成一个对象,然后再从这个对象上读取这三个方法。
这种称作运行时加载,因为只有运行时才能获取这三个方法。
ES6模块不是对象,而是通过export命令显示指定输入代码,再通过import命令输入。
例如:
// ES6模块
import { stat, exists, readFile } from 'fs';
这里实质上是从fs模块加载3个方法,其他方法不加载。这种加载称为编译时加载。
(即ES6编译时就完成模块加载)
除了静态加载带来的好处,ES6还有以下好处
1:不再需要UMD格式了。
2:将来浏览器的新API就能通过模块格式提供,不再必须变成全局或者navigator对象属性
3:不再需要对象作为命名空间(比如Math对象),这些功能都可以通过模块提供。
注意点:模块中自动采用严格模式
模块功能主要由两个命令构成 export和import
export 用于规定模块的对外接口
一个模块就是一个独立的文件,文件里面的变量外面无法获取,如果我们希望外部能够读取模块内部的某个变量,就必须使用export,输出该变量
以下几种方式都可以对外输出变量
1:export var firstName = 'Michael'
// 优先考虑下面这种方法,可以写在脚本尾部,导出什么变量,清晰可见
2: var firstName = 'Michael' export {firstName}
除了输出变量,还可以输出函数或类
export function fun(){}
输出时,还可以对变量进行重命名:
// 这里通过as关键字,重命名了函数fun的对外接口为myFun
export{ fun as myFun}
这里值得注意的是,export命令规定的是对外接口。必须与模块内部的变量建立一一对应关系。
而不是一个值。
// 这种就不行,因为不能直接export一个值
export 1
// 这种也不行,虽然通过变量m,可是还是export一个值
var m = 1; export m
那么如何建立对应的意义关系呢?
可以理解成,export后面的是一份名单,或者是这份名单上的某个成员,
我们需要通过名单上面的名字,找到当前模块的对应的成员。
(即export后面是成员的名字,而不是成员,外部需要通过这个名字,找到这个模块对应的成员)
// 写法一:名字是m
export var m = 1;
// 写法二:名字也是m
var m = 1;
export {m};
// 写法三:名字还是m
var n = 1;
export {n as m};
export语句输出的接口,与其对应的值是动态绑定关系,可以获取模块内部实时的值
export可以出现在模块顶层的任何地方,因为处于块级作用域,就无法做到静态了
import 用于输入其他模块提供的功能
当我们有一个module对外export接口时,我们在这个模块的外部就可以通过import,读取或使用该接口对应的值或函数,类了。
import {} from './profile.js'
Tips:
1:import 接受一对大括号,里面指定要从其他模块导入的变量名,
变量名必须与被导入模块的接口名相同
2:也可以在变量名后使用as进行重命名
3:不允许在加载模块的脚本,改写接口,如果加载的是一个对象,则可以改写对象的属性
4:from后面指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略
5:import命令具有提升效果,会提升到整个模块的头部,首先执行
6:不许使用表达式和变量,这些只有在运行时才能得到的结果的语法结构
7:import会执行所加载的模块,如果多次重复执行一句语句,也只会执行一次
export default命令--模块的默认输出
有时在我们不需要其他使用我们编写的模块的时候,可以通过输出默认值,给使用者使用
export defualt就是用来指定一个模块的默认输出的
例如:
// export-default.js
export default function () {
console.log('foo');
}
// 这个时候其他模块加载该模块时,import命令可以为该匿名函数指定任意名字
// 并且import命令后面,不使用大括号
// import-default.js
import customName from './export-default';
customName(); // 'foo'
// export default命令也可以写在非匿名函数之前
function foo() {
console.log('foo');
}
// 这个时候,foo这个接口在模块外部是无效,加载的时候,视作为匿名函数加载
// 并且export default命令只能使用一次
export default foo;
如上面的例子,foo这个接口在模块外是无效的,主要原因是:
export default就是输出一个叫做default的变量或方法,然后系统允许为它取任意的名字,
default实际上就是我们对外接口,因此default后面不能跟变量声明语句
// 正确
export var a = 1
// 正确
var a = 1
export default a
//错误 此处的变量已经是default了,所以default后面应该跟着a对应的值,而不是a
export default var a = 1
如果想在一个import语句中,同时输出默认方法和其他接口,可以写成
import _,{each,forEach} from 'loash'
export default 也可以用来输出类
export 与 import的复合写法
在一个模块中,先输入,后输出同一个模块,import和export语句写在一起。
// 这里export和import语句结合在一起,写成一行。
// 不过,写成一行以后,foo,bar实际上没有导入到当前模块,只是相当于对外转发这两个接口
// 所以当前模块不能直接使用foo,bar
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
// 默认接口的写法如下。
export { default } from 'foo';
// ES2020 之前,有一种import语句,没有对应的复合写法。
import * as someIdentifier from "someModule";
// ES2020补上了这个写法。
export * as ns from "mod";
// 等同于
import * as ns from "mod";
export {ns};
模块的继承:
// 假设现在编写一个circleplus模块,继承circle模块
export * from 'circle.js'
export var e = 2.71828;
// 因为export * 会忽略circle模块的default方法,所以可以在这里编写输出方法
// 当然import * 也会忽略,所以使用import *导入方法时,需要再import一次,导入模块默认导出值
export default function(x){return Math.exp(x)}
跨模块常量
const声明的常量,只在当前代码块有效,如果想设置跨模块常量,可以按照下面的写法
// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
import() --使ES6的模块动态化
在ES2020提案中,引入import()函数,支持动态加载模块。
// specifier,指定所要加载的模块的位置
// import也可以运用在async函数中
import(specifier)
import()模块返回一个Promise对象
因此,我们可以在then方法的参数中,获取这个模块对应的对象
与Node的require不同的是,import()是异步加载,Node的require是同步加载