概述
javascript一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方式将它们拼装起来。在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。前者用于服务器,后者用于浏览器。ES6在语言规格的层面上实现了模块功能,完全可以取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块都只能在运行时确定这些东西。比如,CommonJS模块就是对象,输入时必须查找对象属性。
let {stat, exists} = require('fs')
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象,然后再从这个对象上面读取两个方法。这种加载被称为"运行时加载",因为只有运行时才能得到这个对象,导致完全没有办法在编译时进行"静态优化"。
ES6模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
import {stat, exists} from 'fs'
上面代码的实质是从fs模块加载3个方法,而不是加载其他方法。这种加载称为"编译加载"或静态加载,即ES6可以在编译时就完成模块加载,效率比CommonJS模块加载方式高。当然,这也导致了ES6模块本身无法被引用,因为它不是对象。
export命令
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
// profile.js
// 输出
export let firstName = "Michael"
export let lastName = "Jackson"
或
let firstName = "Michael"
let lastName = "Jackson"
export {firstName, lastName}
// 输入
import {firstName, lastName} from 'profile.js'
上面代码我们看到export输出代码,import后面必须跟大括号,而且大括号内的变量必须跟输出的变量对应,这里可以理解为解构赋值。最后export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域就会报错,import也是如此。
import命令
import的执行早于函数的调用,这种行为的本质是import命令是编译阶段执行的,在代码运行之前。由于import是静态执行,所以不能使用表达式和变量,只有在运行时才能得到结果的语法结构。
// 报错
import {'f' + 'oo'} from 'module'
// 报错
let module = 'my_module'
import {foo} from module
// 报错
if (x===1) {
import {foo} from 'module1'
} else {
import {foo} from 'module2'
以上三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是无法得到值的。
import语句会执行所加载的模块,所以可以这么写import 'lodash',这个代码仅仅执行lodash模块,但是不会输入任何值。
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载(即星号*)来指定一个对象,所有输出值都加载在这个对象上。
// circle.js
import * as circle from './circle'
export default命令
// demo.js
export default function () {
console.log('foo')
}
import custom from './demo.js'
custom() // foo
注意:export default后面跟任意数据类型或变量,import命令这时候后面不使用大括号。
// 第一组
export default function crc32 () {}
import crc32 from 'crc32'
// 第二组
export function crc32 () {}
import {crc32} from 'crc32'
上面的两组写法中,第一组使用export default对应的import语句后面不跟大括号,第二组使用export对应的import语句需要使用大括号。
export default命令用于指定模块的默认输出。显然一个模块只能有一个默认输出,因此这个命令在一个文件中只能使用一次。所以import命令后面才不用加大括号,因为只可能对应一个方法。本质上,export default就是输出一个叫作default的变量或方法,然后系统允许我们为它取任意名字。
function add (x, y) {
return x*y
}
export {add as default}
// 等同于
export default add
import {default as xxx} from 'modules'
// 等同于
import xxx from 'modules'
正是因为export default命令其实只是输出一个叫作default的变量,所以它后面不能跟变量生命语句。
// 正确
export let a = 1
// 正确
let a = 1
export default a
// 错误
export default let a = 1
上面的代码中,export default中的a的含义是将变量a的值赋给变量default。所以最后一种写法会报错。
// 正确
export default 42
//错误
export 42
上面的代码中,后一句报错是因为没有指定对外的接口,而前一句指定对外接口为default。
静态和动态:编译时加载代码叫静态,运行时加载代码叫动态。
export
运行和编译
静态和动态
ES6 export export default Node require module export区别
总结:export后必须跟接口名以及值,或者是一个对象,import后面必须跟大括号,并且大括号里面必须跟export的接口名对应,export default后面跟任意数据类型都可以,import后不能有大括号,表示整体输入,module export后面需要跟对象,import后面可以跟大括号也可以不跟大括号,有大括号就是解构赋值,没有的话就是整体输入。export输出指定的接口,export default输出一个整体。
网友评论