ES6 Module

103 阅读4分钟

(1)require

let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile

上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象_fs,然后在从这个对象上取3个方法。这种方法称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没有办法做“静态优化”

(2)import

语法:import 命令接受一对大括号,里面指定要从其他模块导入的变量名。

import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为"编译时加载"或者静态加载。

可以使用as关键字,将输入的变量重新命名

import { lastName as surname } from './profile.js';
import {a} from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;

a.foo = 'hello'; // 合法操作

import 命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面改写接口。

上面的代码中,a的属性可以改写成功,并且其他模块也可以读到改写的后的值。不过这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。

import 命令具有提升效果,会提升到整个模块的头部,首先执行。

foo();

import { foo } from 'my_module';

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import 命令是编译阶段执行的,在代码运行前面。所以下面的代码会报错。

// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

Q: 如果在业务中有需求需要根据不同条件导入,我们应该怎么做那?应该用import()函数,后面有介绍。

(3)export

export 命令后面,使用大括号指定所要输出的一组变量

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

也可以这样写

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

可以使用as为输出的变量重新赋值

function v1() { ... }
function v2() { ... }

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

export 语句输出的接口,与其对应的值是动态绑定关系

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

上面的代码输出变量foo,值为bar,500ms 之后变成baz

export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于模块作用域内,就会报错。

function foo() {
  export default 'bar' // SyntaxError
}
foo()

(4)模块的整体加载

import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

注意: 模块整体加载所在的那个对象(上例是circle)应该是可以静态分析的,所以不允许运行时改变。下面的写法都是不允许的

import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};

(5)import()

ES2020提案 引入import()函数,支持动态加载模块。

import()函数是异步加载,require方法是同步加载。

下面是require 动态加载功能

const path = './' + fileName;
const MyModual = require(path)

个人理解:异步加载是还没加载完毕就执行后面的代码,同步加载是等加载完,再执行后面的代码

下面的是import()的一些适用场合 (1) 按需加载

button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});

上面的代码中,import()方法放在click 事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。

(2) 条件加载 import() 可以放在if 代码块中,根据不同的情况,加载不同的模块

if(condition) {
     import('moduleA').then(...);
} else {
    import('moduleB').then(...)
}

上面的代码中,如果满足条件,就加载模块A,否则加载模块B

(3) 动态的模块路径 import()允许模块路径动态生成

import(f())
.then(...)

上面的代码中,根据函数f的返回结果,加载不同的模块。

(4) 对象的解构赋值 import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此可以使用对象的解构赋值的语法,获取输出接口。

import('./myModule.js)
.then(({export1, export2}) => {
})

(5) 如果模块中有default 输出接口,可以用参数直接获得。

import('./myModule')
.then(myModule => {
  console.log(myModule.default)
})

(6) 如果同时加载多个模块,可以采用下面的写法。

Promise.all([
  import('./module1.js'),
  import('./module2.js'),
  import('./module3.js'),
])
.then(([module1, module2, module3]) => {
   ···
});

(7) import()也可以用在async函数之中

async function main() {
  const myModule = await import('./myModule.js');
  const {export1, export2} = await import('./myModule.js');
  const [module1, module2, module3] =
    await Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ]);
}
main();