ES6 : Module的加载
ES6模块与CommonJS模块的差异
- CommonJS输出的是一个值的复制值,ES6模块输出的是一个值的引用,也就是说,对CommonJS模块输出的值进行更改,不会影响到其模块本身的值,而ES6则会影响对同一个模块引用的值。
- CommonJS是运行时加载,ES6是编译时输出接口。
import命令加载CommonJS模块
-
在Node环境中,使用import命令加载CommonJS模块,Node将会自动将module.exports属性当作模块的默认输出,等同于export default。
// a.js module.exports = { foo : "hello" } // 等同于 module export { foo : "hello" } // 引入 import baz from './a'; import { default as baz} from './a'; // baz === { foo : "hello"};
- 如果采用整体输入的方法,default将会取代module.exports作为输入的接口
```js
import * as baz from './a';
// baz === {
// get default(){
// return module.exports;
// },
// get foo(){
// return {this.default.foo}.bind(baz);
// }
//}
- 上面的例子中,this.default代替了module.exports,同时也不难看出,import语法会给baz添加default属性。
require命令加载ES6模块
-
采用require加载ES6模块时,ES6模块的所有输出接口都会成为输入对象的属性
// a.js let foo = { bar : "hello" } export default foo; // b.js const amodule = require('./a.js'); // amodule.default === {bar : "hello"}
循环加载
- 所谓循环加载,就是两个模块互相引用,导致程序递归执行,而两种模块对此的处理方法不同。
CommonJS的循环加载
-
CommonJS的模块加载原理:cjs模块无论被加载多少次,都只会在第一次加载的时候运行一次,以后加载的都是第一次返回的结果,除非手动清除缓存;
// a.js export.done = false; let b = require('./b'); console.log('在a.js中,b.done = %j',b.done); export done = true; console.log('在a.js中,b.done = %j',b.done); console.log('a.js执行完毕'); // b.js export.done = false; let a = require('./a'); console.log('在b.js中,a.done = %j',a.done); export done = true; console.log('在b.js中,a.done = %j',a.done); console.log('b.js执行完毕');
-
在上面的代码中,a.js执行到第二行就会去执行b.js,而b.js执行到第二行就会去执行a.js,系统会自动去a.js寻找对应的export值,但是a.js还没有执行完毕,因此,从exports值只能取出已经执行了的值。
// main.js var a = require('./a'); var b = require('./b'); console.log('在main.js中,a.done = %j,b.done = %j',a.done,b.done); // 输出 在b.js中,a.done = false; b.js执行完毕; 在a.js中,b.done = true; a.js执行完毕; 在main.js中,a.done = true,b.done = true;
-
以上代码证明,main文件执行到第二行是不会再重新执行一次b.js,而是直接返回它最终的值。
ES6模块的循环加载
-
由于ES6模块为动态引用,变量不会缓存,而是指向引用的对象。
// a.js import { bar} from "./b.js"; console.log("a.js"); console.log(bar); export let foo = 'foo'; // b.js import { foo} from "./a.js"; console.log("b.js"); console.log(foo); export let bar = "bar"; // 执行a.js b.js undefined a.js bar
-
以上代码,a.js执行,第一行代码引用b.js,因此运行b.js,而b.js的第一行代码是引入a.js,由于a.js已经运行,所以不会再加载一次,因此输出了undefined,随后正常执行。
两种模式的比较
// a.js
import {
bar} from "./b.js"
export function foo(){
console.log('foo');
bar();
console.log('执行完毕');
}
foo();
// b.js
import {
foo} from "./a.js"
export function bar(){
console.log('bar');
if(Math.random() > 0.5){
foo();
}
}
- 以上这段代码,在CommonJS的标准下是无法执行的。a加载b,然后b又加载a,此时a还未执行结束,所以输出的结果将为null,即对b的foo的值为
null
,因此无法执行,将会报错; - 而在ES6标准下,由于import建立的是对象的引用,因此在a加载时,第一行代码建立了对b.js的
bar
的引用,运行到第四行代码时,将会跳到b.js中执行bar()
;随后再执行下一行代码的console。