子曰:最终的杀器-ES6 模块
概述
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 模块不是对象,而是通过export 命令显式指定输出的代码,再通过import 命令输入。
| 1 | // ES6 模块 | 
上面代码的实质是从fs 模块加载3 个方法,其他方法不加载。这种加载称为“编译时加载” 或者静态加载,即ES6 可用于在编译时就完成模块加载,效率要比CommonJS 模块的加载方式高。
由于ES6 模块是编译时加载,使得静态分析成为可能。
需要注意this 的限制。ES6 模块之后,顶层的this 指向undefined,即不应该在顶层代码使用this。
export 命令
模块功能主要由两个命令构成:export 和import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。使用export 命令输出对外接口。
| 1 | // profile.js | 
ES6 将profile.js 视为一个模块,export 命令对外输出了3 个变量。
export 的另一种写法。
| 1 | // profile.js | 
接口名与模块内部变量之间,必须建立一一对应的关系
| 1 | // 报错 | 
另外,export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到内部实时的值。
| 1 | export var foo = 'bar' | 
上面代码输出变量foo,值为bar, 500 毫米之后变成baz。
子曰:前面说了,import 就相当于将代码复制了过来,所以
setTimeout的代码仍然可以影响foo 的值
import 命令
使用export 命令定义了模块的对外接口以后,其他JS 文件就可以通过import 命令加载这个模块。
| 1 | // main.js | 
还可以使用as 关键字,将输入的变量重命名。
| 1 | import { lastName as surname } from './profile.js' | 
注意:import 命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里,改写接口。
| 1 | import {a} from './xxx.js' | 
由于import 是静态执行,所以不能使用表达式和变量,这些只能在运行时才能得到结果的语法结构。
| 1 | // 报错 | 
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
| 1 | // circle.js | 
现在加载这个模块。
| 1 | // math.js | 
整体加载方法如下。
| 1 | import * as circle from './circle' | 
export default 命令
使用export default 命令,为模块指定默认输出。
| 1 | // export-default.js | 
其他模块加载该模块时,import 命令可以为该匿名函数指定任意名字。
| 1 | // import-default.js | 
需要注意的是,这时import 命令后面,不使用大括号。
正因为export default 明in个其实是输入一个叫做default 的变量,所以它后面不能跟变量声明语句。
| 1 | // 正确 | 
因为export default 命令的本质是将后民安的值,赋给default 变量,所以可以直接将一个值写在export default 之后。
| 1 | // 正确 | 
export default 也可以用来输出类。
| 1 | // MyClass.js | 
export 与import 的复合写法
如果在一个模块中,先输入后输出一个模块,import 语句可以与export 语句写在一起。
| 1 | export { foo, bar } from 'my_module' | 
需要注意的是,写成一行以后,foo 和bar 实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo 和bar。
跨模块常量
const 声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
| 1 | // constants.js 模块 | 
如果要使用的变量非常多,可以建立一个专门的constants 目录,将各种变量写在不同的文件里面。
| 1 | // constants/db.js | 
然后,将这些文件输出的常量,合并在index.js 里面。
| 1 | // constants/index.js | 
使用的时候,直接加载index.js 就可以了
| 1 | // script.js | 
import()
简介
import 命令会被JavaScript 引擎静态分析,先于模块内的其他语句执行(import 命令叫做“连接binding” 其实更合适)。
| 1 | // 报错 | 
上面代码中,引擎处理import 是在编译时,这是不会去分析或执行if 语句,所以import 语句放在if 语句块之中毫无意义,因此会报语法错误,而不是执行错误。也就是说,import 和export 命令只能在模块的顶层,不能在代码块之中。
因此,有一个提案,建议引入import() 函数,完成动态加载。
| 1 | import(specifier) | 
上面代码中,import 函数的参数specifier,指定所要加载的模块的位置。import 命令能够接受什么参数,import() 函数就能接受什么参数,两者的主要区别在于后者是动态加载。
import() 返回一个Promise 对象。
| 1 | const main = document.querySelector('main') | 
- import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。
- 另外,import()函数与所加载的模块没有静态连接关系,这点也是与import命令不同。
- import()类似于Node 的- require方法,区别主要是前者是异步加载,后者是同步加载。
注意点
import() 加载模块成功以后,这个模块会作为一个对象,当作then 方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
| 1 | import('./myModule.js') | 
如果想同时加载多个模块,可以采用下面的写法。
| 1 | Promise.all([ | 
import() 也可以用在async 函数之中。
| 1 | async function main() { |