@@ -359,28 +359,26 @@ let o = new MyClass();
359359假设有一个circleplus模块,继承了circle模块。
360360
361361``` javascript
362-
363362// circleplus.js
364363
365364export * from ' circle' ;
366365export var e = 2.71828182846 ;
367366export default function (x ) {
368367 return Math .exp (x);
369368}
370-
371369```
372370
373- 上面代码中的“ export * ”,表示输出circle模块的所有属性和方法, export default命令定义模块的默认方法 。
371+ 上面代码中的` export * ` ,表示输出 ` circle ` 模块的所有属性和方法, ` export default ` 命令定义模块的默认方法 。
374372
375- 这时,也可以将circle的属性或方法 ,改名后再输出。
373+ 这时,也可以将 ` circle ` 的属性或方法 ,改名后再输出。
376374
377375``` javascript
378376// circleplus.js
379377
380378export { area as circleArea } from ' circle' ;
381379```
382380
383- 上面代码表示,只输出circle模块的area方法,且将其改名为circleArea 。
381+ 上面代码表示,只输出 ` circle ` 模块的 ` area ` 方法,且将其改名为 ` circleArea ` 。
384382
385383加载上面模块的写法如下。
386384
@@ -392,7 +390,216 @@ import exp from "circleplus";
392390console .log (exp (math .E ));
393391```
394392
395- 上面代码中的"import exp"表示,将circleplus模块的默认方法加载为exp方法。
393+ 上面代码中的` import exp ` 表示,将` circleplus ` 模块的默认方法加载为` exp ` 方法。
394+
395+ ## 循环加载
396+
397+ “循环加载”(circular dependency)指的是,` a ` 脚本的执行依赖` b ` 脚本,而` b ` 脚本的执行又依赖` a ` 脚本。
398+
399+ ``` javascript
400+ // a.js
401+ var b = require (' b' );
402+
403+ // b.js
404+ var a = require (' a' );
405+ ```
406+
407+ 通常,“循环加载”表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行,因此应该避免出现。
408+
409+ 但是实际上,这是很难避免的,尤其是依赖关系复杂的大项目,很容易出现` a ` 依赖b,` b ` 依赖` c ` ,` c ` 又依赖` a ` 这样的情况。这意味着,模块加载机制必须考虑“循环加载”的情况。
410+
411+ 对于JavaScript语言来说,目前最常见的两种模块格式CommonJS和ES6,处理“循环加载”的方法是不一样的,返回的结果也不一样。
412+
413+ ### CommonJS模块
414+
415+ CommonJS模块的重要特性是加载时执行,即脚本代码在` require ` 的时候,就会全部执行。因此,CommonJS规定,一旦发现某个模块被“循环加载”,就立即停止加载,只输出已经执行的部分。
416+
417+ 让我们来看,Node[ 官方文档] ( https://nodejs.org/api/modules.html#modules_cycles ) 里面的例子。脚本文件` a.js ` 代码如下。
418+
419+ ``` javascript
420+ exports .done = false ;
421+ var b = require (' ./b.js' );
422+ console .log (' 在 a.js 之中,b.done = %j' , b .done );
423+ exports .done = true ;
424+ console .log (' a.js 执行完毕' );
425+ ```
426+
427+ 上面代码之中,` a.js ` 脚本先输出一个` done ` 变量,然后加载另一个脚本文件` b.js ` 。注意,此时` a.js ` 代码就停在这里,等待` b.js ` 执行完毕,再往下执行。
428+
429+ 再看` b.js ` 的代码。
430+
431+ ``` javascript
432+ exports .done = false ;
433+ var a = require (' ./a.js' );
434+ console .log (' 在 b.js 之中,a.done = %j' , a .done );
435+ exports .done = true ;
436+ console .log (' b.js 执行完毕' );
437+ ```
438+
439+ 上面代码之中,` b.js ` 执行到第二行,就会去加载` a.js ` ,这时,就发生了“循环加载”。为了避免无穷递归,执行引擎不会去再次执行` a.js ` ,而是只返回已经执行的部分。
440+
441+ ` a.js ` 已经执行的部分,只有一行。
442+
443+ ``` javascript
444+ exports .done = false ;
445+ ```
446+
447+ 因此,对于` b.js ` 来说,它从` a.js ` 只输入一个变量` done ` ,值为` false ` 。
448+
449+ 然后,` b.js ` 接着往下执行,等到全部执行完毕,再把执行权交还给` a.js ` 。于是,` a.js ` 接着往下执行,直到执行完毕。我们写一个脚本` main.js ` ,验证这个过程。
450+
451+ ``` javascript
452+ var a = require (' ./a.js' );
453+ var b = require (' ./b.js' );
454+ console .log (' 在 main.js 之中, a.done=%j, b.done=%j' , a .done , b .done );
455+ ```
456+
457+ 执行` main.js ` ,运行结果如下。
458+
459+ ``` bash
460+ $ node main.js
461+
462+ 在 b.js 之中,a.done = false
463+ b.js 执行完毕
464+ 在 a.js 之中,b.done = true
465+ a.js 执行完毕
466+ 在 main.js 之中, a.done=true, b.done=true
467+ ```
468+
469+ 上面的代码证明了两件事。一是,在` b.js ` 之中,` a.js ` 没有执行完毕,只执行了第一行。二是,` main.js ` 执行到第二行时,不会再次执行` b.js ` ,而是输出缓存的` b.js ` 的执行结果,即它的第四行。
470+
471+ ``` javascript
472+ exports .done = true ;
473+ ```
474+
475+ ### ES6模块
476+
477+ ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令` import ` 时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值。
478+
479+ 因此,ES6模块是动态引用,不存在缓存值的问题,而且模块里面的变量,绑定其所在的模块。还是举本章开头时的例子。
480+
481+ ``` javascript
482+ // m1.js
483+ export var foo = ' bar' ;
484+ setTimeout (() => foo = ' baz' , 500 );
485+
486+ // m2.js
487+ import {foo } from ' ./m1.js' ;
488+ console .log (foo);
489+ setTimeout (() => console .log (foo), 500 );
490+ ```
491+
492+ 上面代码中,` m1.js ` 的变量` foo ` ,在刚加载时等于` bar ` ,过了500毫秒,又变为等于` baz ` 。
493+
494+ 让我们看看,` m2.js ` 能否正确读取这个变化。
495+
496+ ``` bash
497+ $ babel-node m2.js
498+
499+ bar
500+ baz
501+ ```
502+
503+ 上面代码表明,ES6模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
504+
505+ 这导致ES6处理“循环加载”与CommonJS有本质的不同。ES6根本不会检查是否发生了“循环加载”,只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
506+
507+ 请看下面的例子(摘自 Dr. Axel Rauschmayer 的[ 《Exploring ES6》] ( http://exploringjs.com/es6/ch_modules.html ) )。
508+
509+ ``` javascript
510+ // a.js
511+ import {bar } from ' ./b.js' ;
512+ export function foo () {
513+ bar ();
514+ console .log (' 执行完毕' );
515+ }
516+ foo ();
517+
518+ // b.js
519+ import {foo } from ' ./a.js' ;
520+ export function bar () {
521+ if (Math .random () > 0.5 ) {
522+ foo ();
523+ }
524+ }
525+ ```
526+
527+ 按照CommonJS规范,上面的代码是没法执行的。` a ` 先加载` b ` ,然后` b ` 又加载` a ` ,这时` a ` 还没有任何执行结果,所以输出结果为` null ` ,即对于` b.js ` 来说,变量` foo ` 的值等于` null ` ,后面的` foo() ` 就会报错。
528+
529+ 但是,ES6可以执行上面的代码。
530+
531+ ``` bash
532+ $ babel-node a.js
533+
534+ 执行完毕
535+ ```
536+
537+ ` a.js ` 之所以能够执行,原因就在于ES6加载的变量,都是动态引用其所在的模块。只要引用是存在的,代码就能执行。
538+
539+ 我们再来看ES6模块加载器[ SystemJS] ( https://github.com/ModuleLoader/es6-module-loader/blob/master/docs/circular-references-bindings.md ) 给出的一个例子。
540+
541+ ``` javascript
542+ // even.js
543+ import { odd } from ' ./odd'
544+ export var counter = 0 ;
545+ export function even (n ) {
546+ counter++ ;
547+ return n == 0 || odd (n - 1 );
548+ }
549+
550+ // odd.js
551+ import { even } from ' ./even' ;
552+ export function odd (n ) {
553+ return n != 0 && even (n - 1 );
554+ }
555+ ```
556+
557+ 上面代码中,` even.js ` 里面的函数` foo ` 有一个参数` n ` ,只要不等于0,就会减去1,传入加载的` odd() ` 。` odd.js ` 也会做类似操作。
558+
559+ 运行上面这段代码,结果如下。
560+
561+ ``` javascript
562+ $ babel- node
563+ > import * as m from ' ./even.js' ;
564+ > m .even (10 );
565+ true
566+ > m .counter
567+ 6
568+ > m .even (20 )
569+ true
570+ > m .counter
571+ 17
572+ ```
573+
574+ 上面代码中,参数` n ` 从10变为0的过程中,` foo() ` 一共会执行6次,所以变量` counter ` 等于6。第二次调用` even() ` 时,参数` n ` 从20变为0,` foo() ` 一共会执行11次,加上前面的6次,所以变量` counter ` 等于17。
575+
576+ 这个例子要是改写成CommonJS,就根本无法执行,会报错。
577+
578+ ``` javascript
579+ // even.js
580+ var odd = require (' ./odd' );
581+ var counter = 0 ;
582+ exports .counter = counter;
583+ exports .even = function (n ) {
584+ counter++ ;
585+ return n == 0 || odd (n - 1 );
586+ }
587+
588+ // odd.js
589+ var even = require (' ./even' );
590+ exports .odd = function (n ) {
591+ return n != 0 && even (n - 1 );
592+ }
593+ ```
594+
595+ 上面代码中,` even.js ` 加载` odd.js ` ,而` odd.js ` 又去加载` even.js ` ,形成“循环加载”。这时,执行引擎就会输出` even.js ` 已经执行的部分(不存在任何结果),所以在` odd.js ` 之中,变量` even ` 等于` null ` ,等到后面调用` even(n-1) ` 就会报错。
596+
597+ ``` bash
598+ $ node
599+ > var m = require(' ./even' );
600+ > m.even(10)
601+ TypeError: odd is not a function
602+ ```
396603
397604## ES6模块的转码
398605
0 commit comments