js之module


模块加载方案

  • CommonJS: 通过require来引入模块,通过module.exports输出接口。用于服务端,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快
  • AMD: 采用异步加载的方式来加载模块,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js实现了AMD规范
  • CMD: sea.js实现了CMD规范,异步加载模块
  • ESModule: 使用import和export的形式来导入导出模块

比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// AMD和CMD
AMD: 依赖前置,定义模块的时候就要声明依赖的模块。依赖模块加载完就立即执行依赖模块
CMD: 就近依赖,用到某个模块再去require。依赖模块加载完后不执行,进入回调后遇到require语句才执行

// AMD 默认推荐
define(["./a", "./b"], function(a, b) {
// 依赖必须一开始就写好
a.doSomething();
// 此处略去 100 行
b.doSomething();
// ...
});

// CMD
define(function(require, exports, module) {
var a = require("./a");
a.doSomething();
// 此处略去 100 行
var b = require("./b"); // 依赖可以就近书写
b.doSomething();
// ...
});

// CommonJs:模块输出的是值。在运行时才能生成一个对象,然后从这个对象上读取方法,称为运行时加载
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
// CommonJs整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。
// 支持动态导入,也就是 require(${path}/xx.js),Es6模块不支持
// CommonJs是值拷贝,想要更新,必须重新导入,Es6是导入和导出的值指向同一个内存地址

// ES6:模块输出的是值的引用。在编译时就完成模块加载,这种称编译时加载,效率要比CommonJS模块的加载方式高。
import { stat, exists, readFile } from 'fs';

import和require使用变量引用

1
2
3
4
5
6
// logo地址:@/assets/img/logo.png
let logo = 'assets/img/logo'
this.logo = require(`@/${logo}.png`) // base64地址
import(`@/${logo}.png`).then(res => {
this.logo = res.default // base64地址
})

export 命令

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果外部要读取模块内部的某个变量,就必须使用export关键字输出该变量。
export命令可以出现在模块的任何位置,只要不是块作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};

// 重命名
function v1() { ... }
function v2() { ... }

export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};

export 是可以取到模块内部实时的值

1
2
3
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// 上面代码输出变量foo,值为bar,500 毫秒之后变成baz。

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
import会有提升效果,所以不一定是放到头部

1
2
3
4
5
6
7
8
// main.js
import {firstName, lastName, year} from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}

// 重命名
import { lastName as surname } from './profile.js';

整体加载

1
2
3
4
5
6
7
8
9
// 正常加载
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));

默认输出

1
2
3
4
5
6
7
8
// export-default.js
export default function () {
console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'

import()

import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。
为了动态加载模块,引入了import(),返回一个Promise对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 报错
if (x === 2) {
import MyModual from './myModual';
}

// import()
const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});

import()输出接口

1
2
3
4
5
6
7
8
9
10
11
import('./myModule.js')
.then(({export1, export2}) => {
// ...·
});
// export1和export2都是myModule.js的输出接口,可以解构获得。

// 如果模块有default输出接口,可以用参数直接获得。
import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});

同时加载多个模块

1
2
3
4
5
6
7
8
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});