1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-29 05:42:20 +00:00

docs(module-loader): ES6 循环加载 #542

This commit is contained in:
ruanyf 2017-11-12 11:52:12 +08:00
parent 6d2fc73616
commit eeba1984a8

View File

@ -505,7 +505,7 @@ var a = require('a');
### CommonJS 模块的加载原理
介绍ES6如何处理"循环加载"之前先介绍目前最流行的CommonJS模块格式的加载原理。
介绍 ES6 如何处理“循环加载”之前,先介绍目前最流行的 CommonJS 模块格式的加载原理。
CommonJS 的一个模块,就是一个脚本文件。`require`命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
@ -610,110 +610,72 @@ ES6 处理“循环加载”与CommonJS有本质的不同。ES6模块是动态
请看下面这个例子。
```javascript
// a.js如下
import {bar} from './b.js';
console.log('a.js');
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
// b.js
import {foo} from './a.js';
console.log('b.js');
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';
```
上面代码中,`a.js`加载`b.js``b.js`又加载`a.js`,构成循环加载。执行`a.js`,结果如下。
上面代码中,`a.mjs`加载`b.mjs``b.mjs`又加载`a.mjs`,构成循环加载。执行`a.mjs`,结果如下。
```bash
$ babel-node a.js
b.js
undefined
a.js
bar
$ node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined
```
上面代码中,由于`a.js`的第一行是加载`b.js`,所以先执行的是`b.js`。而`b.js`的第一行又是加载`a.js`,这时由于`a.js`已经开始执行了,所以不会重复执行,而是继续往下执行`b.js`,所以第一行输出的是`b.js`
上面代码中,执行`a.mjs`以后会报错,`foo`变量未定义,这是为什么?
接着,`b.js`要打印变量`foo`,这时`a.js`还没执行完,取不到`foo`的值,导致打印出来是`undefined``b.js`执行完,开始执行`a.js`,这时就一切正常了
让我们一行行来看ES6 循环加载是怎么处理的。首先,执行`a.mjs`以后,引擎发现它加载了`b.mjs`,因此会优先执行`b.mjs`,然后再执行`a.js`。接着,执行`b.mjs`的时候,已知它从`a.mjs`输入了`foo`接口,这时不会去执行`a.mjs`,而是认为这个接口已经存在了,继续往下执行。执行到第三行`console.log(foo)`的时候,才发现这个接口根本没定义,因此报错
再看一个稍微复杂的例子(摘自 Axel Rauschmayer 的[《Exploring ES6》](http://exploringjs.com/es6/ch_modules.html)
解决这个问题的方法,就是让`b.mjs`运行的时候,`foo`已经有定义了。这可以通过将`foo`写成函数来解决
```javascript
// a.js
import {bar} from './b.js';
export function foo() {
console.log('foo');
bar();
console.log('执行完毕');
}
foo();
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.js
import {foo} from './a.js';
export function bar() {
console.log('bar');
if (Math.random() > 0.5) {
foo();
}
}
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};
```
按照 CommonJS 规范,上面的代码是没法执行的。`a`先加载`b`,然后`b`又加载`a`,这时`a`还没有任何执行结果,所以输出结果为`null`,即对于`b.js`来说,变量`foo`的值等于`null`,后面的`foo()`就会报错。
但是ES6可以执行上面的代码。
这时再执行`a.mjs`就可以得到预期结果。
```bash
$ babel-node a.js
$ node --experimental-modules a.mjs
b.mjs
foo
a.mjs
bar
执行完毕
// 执行结果也有可能是
foo
bar
foo
bar
执行完毕
执行完毕
```
上面代码中,`a.js`之所以能够执行原因就在于ES6加载的变量都是动态引用其所在的模块。只要引用存在代码就能执行。
下面,我们详细分析这段代码的运行过程。
这是因为函数具有提升作用,在执行`import {bar} from './b'`时,函数`foo`就已经有定义了,所以`b.mjs`加载的时候不会报错。这也意味着,如果把函数`foo`改写成函数表达式,也会报错。
```javascript
// a.js
// 这一行建立一个引用,
// 从`b.js`引用`bar`
import {bar} from './b.js';
export function foo() {
// 执行时第一行输出 foo
console.log('foo');
// 到 b.js 执行 bar
bar();
console.log('执行完毕');
}
foo();
// b.js
// 建立`a.js``foo`引用
import {foo} from './a.js';
export function bar() {
// 执行时,第二行输出 bar
console.log('bar');
// 递归执行 foo一旦随机数
// 小于等于0.5,就停止执行
if (Math.random() > 0.5) {
foo();
}
}
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
const foo = () => 'foo';
export {foo};
```
上面代码的第四行,改成了函数表达式,就不具有提升作用,执行就会报错。
我们再来看 ES6 模块加载器[SystemJS](https://github.com/ModuleLoader/es6-module-loader/blob/master/docs/circular-references-bindings.md)给出的一个例子。
```javascript
@ -722,13 +684,13 @@ import { odd } from './odd'
export var counter = 0;
export function even(n) {
counter++;
return n == 0 || odd(n - 1);
return n === 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
return n != 0 && even(n - 1);
return n !== 0 && even(n - 1);
}
```