ES6 模块
静态 import
静态 import,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。从而更容易从代码静态分析工具和 tree shaking 中受益。
使用方式
模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。
基本使用方式
对外导出模块
// test.js
export const test = 'a'
// 或者
export { test }
对内导入模块
import { test } from './test.js'
导入的模块命名必须跟导出的时候一样
使用默认方式
对外导出模块
// test.js
const test = 'a'
export default test
对内导入模块
import test from './test.js'
导入的模块命名不需要跟导出的时候一样
重命名方式
export 重命名
// test.js
const test = 'a'
export { v as test}
import 重命名
import { v as V } from './test.js'
模块整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
对外导出模块
// test.js
export function reduce(a, b) {
return a - b
}
export function add(a, b) {
return a + b
}
对内导入模块
import * as count from './count.js'
console.log(count.reduce(2, 1)) // 1
console.log(count.add(1, 2)) // 3
Module 内得 this 指向
模块之中,顶层的this关键字返回undefined,而不是指向window。
TIP
export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新
动态 import(延迟加载)
为了能够实现 JavaScript 引擎的静态分析,所以当初 Es6 的模块设计是静态化的。如果我们把 import 写在代码块中(比如 if 或函数里面),那么就会报语法错误。
function test() {
import count from './count.js'
}
这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。
所以 ES2020 提案 引入 import() 函数,支持动态加载模块。
import()
import() 函数使用很简单,就直接传入参数模块路径即可。
import('./count.js')
支持 Promise
import()返回一个 Promise 对象。所以我们可以很轻松的实现对其模块内容导入成功后再进行相关操作
import('./count.js').then(module => {
module.add(2, 1)
})
// 或者直接解构获值
import('./count.js').then(({ add }) => {
add(2, 1)
})
// 重命名方式
import('./count.js').then(({ add: add2 }) => {
add2(2, 1)
})
当然,还是更推荐使用 await
await import('./count.js')
支持动态模块路径
import()支持动态模块路径
import(f()).then(...)
上面代码中,根据函数f的返回结果,加载不同的模块。
使用场合
按需加载
可以实现在需要的时候再加载模块,例如点击事件:
function click() {
import('./count.js')
}
条件加载
import()可以放在if代码块,根据不同的情况,加载不同的模块。
if (condition) {
import('moduleA').then(...)
} else {
import('moduleB').then(...)
}
浏览器如何加载 ES6 Module
前置知识
默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到 script 标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。
如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。
defer
<script src="module.js" defer></script>
浏览器会异步加载脚本,但是要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行。
async
<script src="module.js" async></script>
浏览器会异步加载脚本,而且一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
ES6 Module 加载规则
浏览器加载 ES6 模块,也使用 script 标签,但是要加入type="module"属性。
<script type="module" src="./module.js"></script>
上面代码在网页中插入一个模块 module.js,由于type属性设为 module,所以浏览器知道这是一个 ES6 模块。
浏览器对于带有 type="module" 的 script,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了script 标签的defer属性。
<script type="module" src="./module.js"></script>
<!-- 等同于 -->
<script type="module" src="./module.js" defer></script>
TIP
动态 import(),它不需要依赖 type="module" 的 script 标签。