1. ES6Module规范
在ES6Module还没出来之前,存在的模块加载方案有CommonJS和AMD,CMD等几种,ES6Module的优点:
- 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
- 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
- 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
那我们来看一下ES6Module的写法:、
1. export的写法
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
等同于
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
除了可以导出变量,还可以导出函数或者类:
export function multiply(x, y) {
return x * y;
};
export class {};
通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名:
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
//上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。
需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
我们在export导出的时候,需要注意两个点:
- 声明定义导出式;
- 大括号导出式
// 报错
export 1;
// 报错
var m = 1;
export m;
上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1。1只是一个值,不是接口。正确的写法是下面这样。
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
上面三种写法都是正确的,规定了对外的接口m。其他脚本可以通过这个接口,取到值1。它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系。
2.import的写法
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
//profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
//main.js
import { firstName, lastName, year } from './profile.js';
//如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './profile.js';
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是,如果a是一个对象,改写a的属性是允许的。
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。
import只是静态执行,不能使用表达式和变量。
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
3.模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
下面是一个circle.js文件,它输出两个方法area和circumference。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
现在,加载这个模块。
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
上面写法是逐一指定要加载的方法,整体加载的写法如下。
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变。下面的写法都是不允许的。
import * as circle from './circle';
// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
4.export defalut 命令
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
上面代码是一个模块文件export-default.js,它的默认输出是一个函数。
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。
export default命令用在非匿名函数前,也是可以的。
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
上面代码中,foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。
下面比较一下默认输出和正常输出。
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。
本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。所以,下面的写法是有效的。
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句(function和class除外)。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
上面代码中,export default a的含义是将变量a的值赋给变量default。所以,最后一种写法会报错。
同样地,因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。
// 正确
export default 42;
// 报错
export 42;
上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定对外接口为default。
有了export default命令,输入模块时就非常直观了,以输入 lodash 模块为例。
import _ from 'lodash';
如果想在一条import语句中,同时输入默认方法和其他接口,可以写成下面这样。
import _, { each, forEach } from 'lodash';
对应上面代码的export语句如下。
export default function (obj) {
// ···
}
export function each(obj, iterator, context) {
// ···
}
export { each as forEach };
上面代码的最后一行的意思是,暴露出forEach接口,默认指向each接口,即forEach和each指向同一个方法。
export default也可以用来输出类。
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();
总结!!!
在webpack中支持CommonJS导出,ES6module导入,也支持ES6module导出,CommonJS导入(但是这个拿到的不是导出的直接值需要转换)
在node中只支持CommonJS规范
在浏览器都不支持,但是给新浏览器加type="module"可以使用ES6module规范
//export.js
var a = 10;
var b = 11;
export default function (){console.log('hahaha')};
export { a, b };
export var c = 12;
//import.js
import fn from './export.js';//相当于导出的default(匿名函数)为fn,此时fn可以执行
fn();//打印出来‘hahahaha’
import { a, b , c } from './export.js'//导入的变量名要和导出的接口一样
console.log(a,b,c);//打印出来为 10 11 12
//可以使用 as 重新命名
import { a as async } from './export.js'
console.log(async);//打印出来为 10
//模块的整个加载
import * as all from './export';
console.log(all.a,all.b,all.c,all.default);//打印出 10 11 12 fn
// all中包含导入的所有东西,包括default
//我们导入的东西一般都是只读属性的值,不能轻易的改变(如果导入的是obj可以修改值,但是强烈不建议修改)
2. CommonJS规范
exports相当于module.exports的引用,默认的module.exports导出是一个空对象,所以exports导出的是一个对象:
//exports.js
exports.fn = function(){console.log('haha')};
//import.js
var a = require('./exports');
console.log(a);//输出a为一个对象
a.fn();//输出为 haha
当我们使用module.exports时再使用exports就没用,因为前者会覆盖后者的行为。我们可以控制module.exports导出的是啥,别人在require的时候都是找到我们module.exports的这个接口。
//exports.js
exports.fn = function () {
console.log('我是CommonJS规范!');
}
exports.a = 12;
//import.js
var obj = require('./exports');
obj.fn();//输出为 ‘我是CommonJS规范!’
console.loig(obj.a);// 输出为 12
//exports.js
exports.fn = function () {
console.log('我是CommonJS规范!');
}
exports.a = 12;
var func = function(){console.log('aaaa')};
module.exports = { func: func }; // =>可以简写为 => module.exports = { func };
//import.js
var obj = require('./exports');
obj.fn();
console.loig(obj.a);//这样这里的obj就没有fn和a属性了,只有func属性,所以这里会报错
console.log(obj.func());//把上面一行注释掉,这里输出为 'aaaa'
module.exports支持导出任何值,导出什么值,导入的就是什么值:
module.exports = 12;//基本值
module.exports = '12';//字符串
module.exports = true;//boolean
module.exports = null;//null
module.exports = undefined;//undefined
module.exports = Symbol();//Symbol
module.exports = BigInt();//BigInt
var fn = function(){console.log('aa')};
module.exports = fn;//函数
class Obj{};
module.exports = Obj;//类