js之module


概念

es6之前,js没有模块体系,制定的模块加载方案,主要是CommonJs和AMD,前者用于服务器,后者用于浏览器。
ES6模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
模块功能主要有2个命令构成:export和import

比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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是异步导入
// CommonJs是值拷贝,想要更新,必须重新导入,Es6是导入和导出的值指向同一个内存地址

// ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
import { stat, exists, readFile } from 'fs';
// 上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

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';

import命令输入的变量不能修改

字符串不能修改,对象可以修改里面的属性,不过不建议,因为修改了,其他文件引入也会修改

只会加载一次文件

1
2
3
4
5
import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';

整体加载

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]) => {
···
});