愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

《ECMAScript6入门》学习笔记之Module

在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 }

同样的,functionclass的输出,也应该这么做,如:

// 报错
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的复合写法

如果在一个模块中,先输入后输出同一个模块,则可以复合exportimport的写法,如:

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'