@@ -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