@@ -359,28 +359,26 @@ let o = new MyClass();
359
359
假设有一个circleplus模块,继承了circle模块。
360
360
361
361
``` javascript
362
-
363
362
// circleplus.js
364
363
365
364
export * from ' circle' ;
366
365
export var e = 2.71828182846 ;
367
366
export default function (x ) {
368
367
return Math .exp (x);
369
368
}
370
-
371
369
```
372
370
373
- 上面代码中的“ export * ”,表示输出circle模块的所有属性和方法, export default命令定义模块的默认方法 。
371
+ 上面代码中的` export * ` ,表示输出 ` circle ` 模块的所有属性和方法, ` export default ` 命令定义模块的默认方法 。
374
372
375
- 这时,也可以将circle的属性或方法 ,改名后再输出。
373
+ 这时,也可以将 ` circle ` 的属性或方法 ,改名后再输出。
376
374
377
375
``` javascript
378
376
// circleplus.js
379
377
380
378
export { area as circleArea } from ' circle' ;
381
379
```
382
380
383
- 上面代码表示,只输出circle模块的area方法,且将其改名为circleArea 。
381
+ 上面代码表示,只输出 ` circle ` 模块的 ` area ` 方法,且将其改名为 ` circleArea ` 。
384
382
385
383
加载上面模块的写法如下。
386
384
@@ -392,7 +390,216 @@ import exp from "circleplus";
392
390
console .log (exp (math .E ));
393
391
```
394
392
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
+ ```
396
603
397
604
## ES6模块的转码
398
605
0 commit comments