Module

背景

产生

基本概念

与传统模块化方法的区别

// ES6模块,实质是从fs模块加载3个方法,其他方法不加载
import { stat, exists, readFile } from 'fs';

模块的优点

基本用法

特性

严格模式

export命令

export

import命令

import {firstName, lastName, year} from './profile';
function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

模块的整体加载

// circle.js,导出方法
export function area(radius) {
  return Math.PI * radius * radius;
}
export function circumference(radius) {
  return 2 * Math.PI * radius;
}
// file2.js,加载方法
// 指定加载
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
// 整体加载
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

export default命令

// file.js 默认输出一个函数
export default function(){
    console.log('default export');
}
// 加载默认export文件
import customName from './file';
customName(); // 'foo'
// 输出
export default function defaultFn() {
  // ...
}
// 输入
import defaultFn from 'defaultFn';

// 输出
export function defaultFn() {
  // ...
};
// 输入
import {defaultFn} from 'defaultFn';
function add(x, y) {
  return x * y;
}
export {add as default};  // 等同于  export default add;

// app.js
import { default as xxx } from 'modules'; // 等同于 import xxx from 'modules';
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
import customName, { otherMethod } from './export-default';
export default 42;
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();

模块的继承

// circleplus.js(假设circleplus模块继承circle模块)
// 方法一
export * from 'circle';  // 输出继承来的所有circle模块的属性和方法,忽略default方法
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}
// 方法二
export { area as circleArea } from 'circle'; // 将circle的属性或方法改名后再输出

// 加载上面circleplus模块
// main.js
import * as math from 'circleplus';  // 加载所有属性和方法
import exp from 'circleplus';  // 加载默认方法为exp
console.log(exp(math.e));

ES6模块加载的实质

// lib.js
var counter = 3;
function incCounter(){
    counter++;
};
modle.exports = {
    get counter(){
        return counter;
    },
    incCounter: incCounter
};
// main.js
var mod = require('./lib');
console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 4
// 实例一
// lib.js
var counter = 3;
function incCounter(){
    counter++;
};
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

// 实例二
// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError:babel-compile都不会通过
// mod.js
function C() {
  this.sum = 0;
  this.add = function () {
    this.sum += 1;
  };
  this.show = function () {
    console.log(this.sum);
  };
}
export let c = new C();
// x.js
import {c} from './mod';
c.add();    
// y.js
import {c} from './mod';
c.show();
// main.js
import './x';
import './y';

循环加载

CommonJS模块的加载原理

CommonJS的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。之后即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。

 // Node内部加载模块后生成的一个对象
 {
      id: '...',   // 加载模块名
      exports: { ... },  // 模块输出的各个接口
      loaded: true,  // 布尔值,表示该模块的脚本是否执行完毕
      ...
 }

CommonJS模块的循环加载

CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,未执行的部分不会输出。

// a.js
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
    
//b.js
console.log('b starting');
exports.done = false;
const a = require('./a.js');  //此时只执行a.js的第一行,发生循环加载,系统会去a.js模块对应对象的exports取值,但还没执行完,因此只能取部分值(一旦require就会生成对象,然后一步步执行,同时往对象加值)。
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
    
// main.js
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');  // 已经加载,只会从缓存中取值
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);
    
// 执行结果
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true

ES6模块的循环加载

ES6模块是动态引用,如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用

// a.js如下
import {bar} from './b.js';  
console.log('a.js'); 
console.log(bar);
export let foo = 'foo';

// b.js
import {foo} from './a.js';
console.log('b.js');
console.log(foo);
export let bar = 'bar';
    
// 执行结果
b.js
undefined
a.js
bar
// a.js
import {bar} from './b.js';  // 建立引用,从b.js引用‘bar’
export function foo() {
  console.log('foo');
  bar();  // 到b.js执行bar
  console.log('执行完毕');
}
foo();  // 执行时,引用都已完全建立

// b.js
import {foo} from './a.js';  // 建立引用,从a.js引用foo
export function bar() {
  console.log('bar');
  if (Math.random() > 0.5) { // 递归执行foo,一旦随机数小于等于0.5就停止执行
    foo();
  }
}
    
// 执行结果
// Math.random() <= 0.5
foo
bar
执行完毕
    
// Math.random() > 0.5
foo
bar
foo
bar
执行完毕
执行完毕

跨模块常量

const声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),可讲常量设置为一个单独的模块,可用以下代码实现

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
    
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
    
// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

ES6模块的转码

浏览器目前还不支持ES6模块,现在要使用需要将其转换为ES5写法,有三种方法:

// a.js
export function foo() {
    console.log('foo');
}

// test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--加载Google的traceur转码器-->
    <script src="http://google.github.io/traceur-compiler/bin/traceur.js"  type="text/javascript"></script>
    <!--加载system.js文件->
    <script src="system.js"></script>
    <script>
         // import a.js
        System.import('a.js').then(function(m) {
           console.log(m.foo());
        });
    </script>
</head>
<body>
</body>
</html>

阅读书籍

  1. UMD
  2. script type=module
  3. Node官方加载文档-使用CommonJs
  4. Es6-symbol加载-使用Module
  5. module-transpiler
  6. SystemJS
  7. Google Traceur
  8. Module