在ES6之前,JavaScript中是没有模块这一概念的。为了适应日益增长的前端工程化需求,ES6中引入了Module的概念。ES6中的模块化思想,是尽量的静态化,使得能够在编译的时候就能够确定模块间的依赖关系、输入输出之间的关系,这是有助于编译时做静态优化的
一、ES6模块特点
import { stat, exists, readFile } from 'fs'
1)ES6模块是编译时加载,所以能够进行静态分析。但是由于是编译时加载,导致无法访问模块自身
2)不再需要UMD模块格式,因为ES6模块是同时指出服务端和浏览器端的,所以将来在服务端和浏览器端便可以用统一的写法来使用模块
3)ES6的模块自动采用严格模式,无论在头部是否加上'use strict'
4)在顶层模块中,this
指向undefined
5)ES6的模块功能主要通过import
命令和export
命令实现,其中,export
命令用于规定模块对外的接口,import
命令用于输入其他模块提供的功能。一个模块就是一个独立的文件,除非暴露接口,否则外部是无法访问模块内的变量、方法的
二、export命令
export
命令用来对外输出,暴露接口给外部使用
示例:
// profile.js
export var name = 'RuphiLau'
export var age = 21
除了使用上述的形式外,还可以使用以下的形式:
var name = 'RuphiLau'
var age = 21
export { name, age }
除了可以导出变量
外,ES6的模块还可以导出函数
或者类
,如:
export function multiply(x, y) {
return x * y
}
默认情况下,export
输出的变量就是本来的名字,如果我们想指定别名,那么可以使用as
关键字,如:
function v1(){}
function v2(){}
export {
v2 as streamV2,
v2 as streamLatestVersion
}
ES6模块对外暴露的是接口,所以应该与模块内的变量建立一一对应的关系,如:
// 以下做法会报错
export 1
// 还是会报错,因为还是相当于直接输出值
var m = 1
export m
// 正确做法
export var m = 1
var m = 1
export { m }
同样的,function
和class
的输出,也应该这么做,如:
// 报错
function f(){}
export f
// 正确
export function f() {}
function f() {}
export { f }
注意: 模块输出的接口,和它对应的值是动态绑定的,所以可以实时获得模块内部的值。所以这一点和CommonJS是不同的,CommonJS获得的是值的缓存,不会动态更新。此外,因为ES6的模块是静态的,所以export
命令,可以放在模块的任何位置(但必须是模块的顶层)
function foo() {
export default 'bar' // 报错
}
foo()
三、import命令
import
命令用于导入在export
命令里对外暴露的接口。如:
import { var1, var2 } from './modulexxx'
注意:{ }
里包含的变量名称,必须与被导入模块中对外暴露的接口中的名称相同。如果想重命名名称,可以使用as
关键字,如:
import { var1 as myVar } from './modulexxx'
1、from中的路径
1)from中的路径,用来指定模块文件的位置,可以是相对路径,也可以是绝对路径
2)from中的路径文件名最后的.js
可以省略
3)from中如果不带路径名,那么必须要有配置文件,来告诉引擎如何获得模块。即import { foo } from 'util'
这种形式
2、import命令提升
对于以下的代码,是可以正常运行的:
foo()
import { foo } from './modulexxx'
这是因为,ES6的模块是编译阶段执行的,所以import
命令早于代码运行之前就执行了,也正是因为这个原因,所以import
命令里不能使用表达式和变量(因为表达式和变量只有在运行时才能确定值)
// 报错
import { 'f'+'oo' } from './modulexxx'
let module = './modulexxx'
import { foo } from module
if(x === 1) {
import { foo } from 'module1'
} else {
import { foo } from 'module2'
}
3、模块的整体加载
可以使用*
来进行模块的整体加载,如:
import { name, age } from './profile'
// 可以用
import * as p from './profile'
// 然后用以下的形式使用
p.name
p.age
其他注意点
1)如果出现同样的import
语句,那么只会执行一次,如
import 'loadash'
import 'loadash'
2)import
语句会执行所加载的模块
3)import
语句是单例模式,所以对于以下的代码,有:
import { foo } from 'xxx'
import { bar } from 'xxx'
// 等同于
import { foo, bar } from 'xxx'
四、其他的一些语法糖
1、export default
使用export default
,可以不需要知道名称就能加载模块(通常的情况下,使用import
需要名称对应),所以可以有如下的情况:
// profile.js
var name = 'RuphiLau'
export default name
// main.js
import myCustomedName from './profile'
我们可以发现,这时候不仅可以自定义名称,还可以省去{ }
使用注意事项:
1)export default
只能使用一次,所以也只能对应一个变量、函数、类
2)本质上,该做法是将输出的变量取名为default
,然后对于名称为default
的变量,是可以任意指定导入名称的,所以,可以有以下的情况:
export { name as default }
// 相当于
export default name
3)因为default
本质上是一个变量,所以export default
后面不能跟变量定义语句
// 报错
export default var name = 'RuphiLau'
4)可以使用export defaut 123
,但是不能使用export 123
5)default可以和其它导出的变量、函数、类并存,如:
// profile.js
var name = 'RuphiLau'
var age = 21
export default name
export { age }
// main.js
import realName, { age } from './profile'
console.log(realName + ' is ' + age) // 输出 RuphiLau is 21
2、export和import的复合写法
如果在一个模块中,先输入后输出同一个模块,则可以复合export
和import
的写法,如:
export { fn1, fn2 } from 'module1'
// 相当于
import { fn1, fn2 } from 'module1'
export { fn1, fn2 }
还可以进行接口改名、整体输出,如:
// 接口改名
export { fn1 as funcName } from 'module1'
// 整体输出
export * from 'module1'
但是对于以下的import
语句,没有复合写法。即:
import * as module from 'someModule'
import module from 'someModule'
import module, { namedModule } from 'someModule'
// 所以提案提出补上三种写法如下
export * as module from 'someModule'
export module from 'someModule'
export module, { namedModule } from 'someModule'
五、模块的继承
模块之间是可以继承的,如现在有一个circleplus
类继承circle
类,那么有:
// circleplus.js
export * from 'circle'
export var PI = 3.1415926
export default function(r) {
return PI*r*r
}
这段代码表示,在circle
模块的基础上,加入了PI
和默认输出(注意:export *
是会忽略掉default
方法的),可以改名后再输出,如:
export { getRadius as radius } from 'circle'
使用方法如:
import * as c from 'circleplus'
import area from 'circleplus'