深入ES Module
- ES Module和CommonJS的模块化有一些不同之处:
- 一方面它使用了import和export关键字;
- 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式;
浏览器中演示ES6的模块化开发,需要加type="module"才能识别main.js为模块
<script src="main.js" type="module"></script>
一、ES Module模块采用export和import关键字来实现模块化:
- export负责将模块内的内容导出;
- import负责从其他模块导入内容;
三种导出方式:
const name = "why";
const age = 18;
const sayHello = function(name) {
console.log("你好" + name);
}
// 导出方式三种
// 1.方式一:
// export const name = "why";
// export const age = 18;
// export const sayHello = function(name) {
// console.log("你好" + name);
// }
// 2.方式二: {}中统一导出
// {}大括号, 但是不是一个对象
// {放置要导出的变量的引用列表}
export {
name,
age,
sayHello
}
// 3.方式三: {} 导出时, 可以给变量起名别
// export {
// name as fName,
// age as fAge,
// sayHello as fSayHello
// }
export default function() {
console.log("对某一个东西进行格式化");
}
三种导入方式:
// 常见的导入方式也是有三种
// 方式一: import {} from '路径';
// import { name, age, sayHello } from './modules/foo.js';
// 方式二: 导出变量之后可以起别名
// import { name as wName, age as wAge, sayHello as wSayHello } from './modules/foo.js';
// import { fName as wName, fAge as wAge, fSayHello as wSayHello } from './modules/foo.js';
// 方式三: * as foo
// import * as foo from './modules/foo.js';
// console.log(foo.name);
// console.log(foo.age);
// foo.sayHello("王小波");
// 演练: export和import结合使用
import { name, age, sayHello } from './modules/bar.js';
console.log(name);
console.log(age);
console.log(sayHello);
// 演练: default export如何导入
import format from './modules/foo.js';
format();
二、Export和import结合使用
- 为什么要这样做呢?
- 在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中;
- 这样方便指定统一的接口规范,也方便阅读;
- 这个时候,我们就可以使用export和import结合使用
// 演练: export和import结合使用
import { name, age, sayHello } from './modules/foo.js';
console.log(name);
console.log(age);
console.log(sayHello);
三、通过import加载一个模块,是不可以在其放到逻辑代码中的
- 为什么会出现这个情况呢?
- 这是因为ES Module在被JS引擎解析时,就必须知道它的依赖关系;
- 由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况;
- 甚至下面的这种写法也是错误的:因为我们必须到运行时能确定path的值;
- 但是某些情况下,我们确确实实希望动态的来加载某一个模块:
- 如果根据不懂的条件,动态来选择加载模块的路径;
- 这个时候我们需要使用 import() 函数来动态加载;
// 演练: import()函数
let flag = true;
if (flag) {
// require的本质是一个函数
// require('')
// 执行函数
// 如果是webpack的环境下: 模块化打包工具: es CommonJS require()
// 纯ES Module环境下面: import(), 这里的import是个函数,返回的是promise
// 脚手架 -> webpack: import()
import('./modules/foo.js').then(res => {
console.log("在then中的打印");
console.log(res.name);
console.log(res.age);
}).catch(err => {
})
}
四、ES Module加载过程
-
ES Module加载js文件的过程是编译(解析)时加载的,并且是异步的:
- 编译时(解析)时加载,意味着import不能和运行时相关的内容放在一起使用:
- 比如from后面的路径需要动态获取;
- 比如不能将import放到if等语句的代码块中;
- 所以我们有时候也称ES Module是静态解析的,而不是动态或者运行时解析的;
-
异步的意味着:JS引擎在遇到import时会去获取这个js文件,但是这个获取的过程是异步的,并不会阻塞主线程继续执行;
- 也就是说设置了 type=module 的代码,相当于在script标签上也加上了 async 属性;
- 如果我们后面有普通的script标签以及对应的代码,那么ES Module对应的js文件和代码不会阻塞它们的执行;
-
ES Module通过export导出的是变量本身的引用:
- export在导出一个变量时,js引擎会解析这个语法,并且创建模块环境记录(module environment record);
- 模块环境记录会和变量进行 绑定(binding),并且这个绑定是实时的;
- 而在导入的地方,我们是可以实时的获取到绑定的最新值的;
-
所以,如果在导出的模块中修改了变化,那么导入的地方可以实时获取最新的变量;
-
注意:在导入的地方不可以修改变量,因为它只是被绑定到了这个变量上(其实是一个常量)
-
如果bar.js中导出的是一个对象,那么main.js中是否可以修改对象中的属性呢?
- 答案是可以的,因为他们指向同一块内存空间;

五、 Node对ES Module的支持
- 在最新的Current版本(v14.13.1)中,支持es module我们需要进行如下操作:
- 方式一:在package.json中配置 type: module(后续学习,我们现在还没有讲到package.json文件的作用)
- 方式二:文件以 .mjs 结尾,表示使用的是ES Module;
- 这里我们暂时选择以 .mjs 结尾的方式来演练:
- 在最新的LST版本(v12.19.0)中,我们也是可以正常运行的,但是会报一个警告:

六、CommonJS和ES Module交互
- 结论一:通常情况下,CommonJS不能加载ES Module
- 因为CommonJS是同步加载的,但是ES Module必须经过静态分析等,无法在这个时候执行JavaScript代码;
- 但是这个并非绝对的,某些平台在实现的时候可以对代码进行针对性的解析,也可能会支持;
- Node当中是不支持的;
- 结论二:多数情况下,ES Module可以加载CommonJS
- ES Module在加载CommonJS时,会将其module.exports导出的内容作为default导出方式来使用;
- 这个依然需要看具体的实现,比如webpack中是支持的、Node最新的Current版本也是支持的;
- 在最新的LTS版本中也不支持;

浙公网安备 33010602011771号