diff --git a/docs/module-loader.md b/docs/module-loader.md index 96362b4..2dbd848 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -449,7 +449,7 @@ CommonJS 模块加载 ES6 模块,不能使用`require`命令,而要使用`im ```javascript // es.mjs -let foo = { bar:'my-default' }; +let foo = { bar: 'my-default' }; export default foo; foo = null; @@ -501,13 +501,13 @@ var a = require('a'); 但是实际上,这是很难避免的,尤其是依赖关系复杂的大项目,很容易出现`a`依赖`b`,`b`依赖`c`,`c`又依赖`a`这样的情况。这意味着,模块加载机制必须考虑“循环加载”的情况。 -对于JavaScript语言来说,目前最常见的两种模块格式CommonJS和ES6,处理“循环加载”的方法是不一样的,返回的结果也不一样。 +对于 JavaScript 语言来说,目前最常见的两种模块格式 CommonJS 和 ES6,处理“循环加载”的方法是不一样的,返回的结果也不一样。 -### CommonJS模块的加载原理 +### CommonJS 模块的加载原理 -介绍ES6如何处理"循环加载"之前,先介绍目前最流行的CommonJS模块格式的加载原理。 +介绍 ES6 如何处理“循环加载”之前,先介绍目前最流行的 CommonJS 模块格式的加载原理。 -CommonJS的一个模块,就是一个脚本文件。`require`命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。 +CommonJS 的一个模块,就是一个脚本文件。`require`命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。 ```javascript { @@ -518,9 +518,9 @@ CommonJS的一个模块,就是一个脚本文件。`require`命令第一次加 } ``` -上面代码就是Node内部加载模块后生成的一个对象。该对象的`id`属性是模块名,`exports`属性是模块输出的各个接口,`loaded`属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。 +上面代码就是 Node 内部加载模块后生成的一个对象。该对象的`id`属性是模块名,`exports`属性是模块输出的各个接口,`loaded`属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。 -以后需要用到这个模块的时候,就会到`exports`属性上面取值。即使再次执行`require`命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。 +以后需要用到这个模块的时候,就会到`exports`属性上面取值。即使再次执行`require`命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。 ### CommonJS 模块的循环加载 @@ -584,9 +584,9 @@ a.js 执行完毕 exports.done = true; ``` -总之,CommonJS输入的是被输出值的拷贝,不是引用。 +总之,CommonJS 输入的是被输出值的拷贝,不是引用。 -另外,由于CommonJS模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。 +另外,由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。 ```javascript var a = require('a'); // 安全的写法 @@ -605,115 +605,77 @@ exports.bad = function (arg) { ### ES6 模块的循环加载 -ES6 处理“循环加载”与CommonJS有本质的不同。ES6模块是动态引用,如果使用`import`从一个模块加载变量(即`import foo from 'foo'`),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。 +ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用`import`从一个模块加载变量(即`import foo from 'foo'`),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。 请看下面这个例子。 ```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); } ``` @@ -751,21 +713,21 @@ true 上面代码中,参数`n`从10变为0的过程中,`even()`一共会执行6次,所以变量`counter`等于6。第二次调用`even()`时,参数`n`从20变为0,`even()`一共会执行11次,加上前面的6次,所以变量`counter`等于17。 -这个例子要是改写成CommonJS,就根本无法执行,会报错。 +这个例子要是改写成 CommonJS,就根本无法执行,会报错。 ```javascript // even.js var odd = require('./odd'); var counter = 0; exports.counter = counter; -exports.even = function(n) { +exports.even = function (n) { counter++; return n == 0 || odd(n - 1); } // odd.js var even = require('./even').even; -module.exports = function(n) { +module.exports = function (n) { return n != 0 && even(n - 1); } ``` @@ -779,9 +741,9 @@ $ node TypeError: even is not a function ``` -## ES6模块的转码 +## ES6 模块的转码 -浏览器目前还不支持ES6模块,为了现在就能使用,可以将转为ES5的写法。除了Babel可以用来转码之外,还有以下两个方法,也可以用来转码。 +浏览器目前还不支持 ES6 模块,为了现在就能使用,可以将转为 ES5 的写法。除了 Babel 可以用来转码之外,还有以下两个方法,也可以用来转码。 ### ES6 module transpiler