# 基本特性
目前,绝大多数浏览器都已经直接支持ES Modules
,因此可以直接通过如下方式使用(为script标签添加**type="module"**即可)
<script type="module">
console.info("hello, ES Modules")
</script>
2
3
上述代码可以正常执行,但它相对于普通的script标签会有一些新的特性
ESM
会自动采用严格模式,忽略'use strict'- 例如:我们再内部打印this,会发现它是undefined,因为在严格模式全局的this,是undefined,而非严格模式下指向window
<script type="module">
console.info(this) // undefined
</script>
2
3
- 每个ES Module 都是运行在单独的私有作用域
- 每个ES Module之间的变量不会互相影响,这样就不会造成全局污染
<script type="module">
var foo = 100
console.info(foo) // 100
</script>
<script type="module">
console.info(foo) // 报错,foo is not defined
</script>
2
3
4
5
6
7
ESM
是通过CORS的方式请求外部JS模块的,如果所在的服务端不支持CORS就会报跨域错误
<!-- 该地址不支持CORS就会报错 -->
<script type="module" src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<!-- 该地址支持CORS,就会去请求 -->
<script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
2
3
4
ESM
不支持文件访问,必须使用http server的方式让其工作ESM
的script标签会延迟执行脚本,等同于添加了一个defer属性- 普通的script标签在HTML中会采用立即执行的机制,也就是包装一层立即执行函数(一个就形成了所谓的调用栈),因此网页会等待script标签加载
<body>
<!-- 同步,alert弹窗不关闭,就不会渲染下面的p标签 -->
<script>
alert("弹窗")
</script>
<p>要显示的内容</p>
<!-- 延迟,alert会在body渲染完后执行 -->
<script defer>
alert("弹窗")
<p>要显示的内容</p>
</script>
<!-- 延迟,alert会在body渲染完后执行 -->
<script type="module">
alert("弹窗")
<p>要显示的内容</p>
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 导入和导出
export 用于导出成员,import用于导入成员
# 导出语法
- export 可以修饰变量、函数、类等的导出
export const name = "foo module"
export function hello() {
console.info("hh")
}
export class Person {}
2
3
4
5
- 更为常见的是通过在后面单独统一导出,这样可以更加直观的表示导出的值有哪些
const name = "foo module"
function hello() {
console.info("hh")
}
class Person {}
// 单独导出
export { name, hello, Person }
2
3
4
5
6
7
8
- 可以将要导出的值重命名,导入时就需要使用重命名后的名字导入
注意,如果重命名为default,那导入时必须重命名,因为default是关键字
// 将name重命名为fooName
export {
name as fooName
hello as default
}
2
3
4
5
- 直接通过default导出默认成员,导入时可以任意命名
export default name
# 导入语法
# import 部分问题
- 在import其他模块时文件路径必须加上.js的扩展名,不能省略(原生ES Modules)
import { name, hello, Person } from "./xx.js"
- index文件引入的时候必须是全路径的,在CommonJS中是可以省略index的
// Es module中
import { name } from "./utils/index.js"
2
当然上述两个问题,在打包工具中都已经帮助我们解决,因此在使用时可以不添加扩展名,也可以不加index.js
3. 引入的问题,相对路径的./
不能省略,省略了会认为是在引入第三方模块
4. 可以使用/
表示网站根路径来引入
// / 表示网站根路径
import { name } from "/04/1.js"
2
- 引入完整的url(直接使用cdn)
import { name } from "https://XXX"
- 可以直接使用空
{}
引入,就相当于直接执行这个模块,就不会去提取成员
import {} from "./1.js"
// 或者
import "./1.js"
2
3
这在我们导入一些不需要外界控制的子功能模块时非常有用
7. 如果一个功能模块在导出时特别多,而且在导入时都会用到,就可以使用*
将模块中所有成员全部提取出来,再使用as
将所有成员全部放在对象中
import * as mod from "./module.js"
# 动态导入
- import关键词不能from一个变量(比如说运行阶段才知道要导入的路径)
- import关键词只能出现在最顶层
所有为了解决这个问题,我们可以使用使用import函数:
import("路径").then(module => {
// import 函数返回值是一个promise
console.info(module); // 回调中获取的值就是模块下所有成员组成的对象
})
2
3
4
# 同时导入default和其他命名成员
import { name, age, default as title } from "./module.js"
// 也可以使用逗号分开default和其他命名成员
// 逗号左边就是default成员,逗号右边就是其他命名成员
import title, { name, age } from "./module.js"
2
3
4
# 将导入的结果作为导出成员
直接将import修改为export
export { name, age } from "./1.js"
// 当然在这个文件中,也不能访问上述成员,因为没有导入进来
2
3
这个特性一般在index文件中用到,在这个文件中集中导出该类模块下所有需要导出的成员,例如components或者actions等
# 导入导出注意事项
- 在使用export导出的{}里的内容并不是对象字面量,引入的时候也不是对这个对象的解构
const name = "Rain"
const age = 18
export { name, age }
2
3
很多人会联想到ES6的对象字面量的用法,但这二者不是同一个东西
export后跟的{}
是一个固定的用法,如果要在后面导出一个对象成员,可以使用 export default,此时后面的内容才是一个对象。
const name = "Rain"
const age = 18
export default { name, age }
2
3
导入时会理解成对象解构,此时就会报错(找不到name和age两个成员),因此可以看出import后面的{}
不是解构,只是一种语法。
同时如果将export {} 的 {}
理解成对象,那export 123就会被认为是正确的,但其实是错误的
- 在ESM中导出一个成员,导出的是这个成员的引用
// module A
const name = "zs"
export { name }
// module B
import { name } from "./1.js"
2
3
4
5
6
在模块A和模块B中的name都是指向相同的地址,导出时只是导出了name的地址,访问的name始终指向的是模块A中定义的name的空间
- 导出的是只读的,即一个常量
# 解决浏览器环境兼容性(Polyfill兼容方案)
ES Modules在2014年被提出,早期浏览器不支持,而且截止到目前为止,很多浏览器都还是不支持,例如:ie,baidu Browser等
当然,借助编译工具是可以解决兼容性的,例如:Browser ES Module Loader。它只需要引入到HTML中,就可以让浏览器支持ES Modules
- 安装
yarn add browser-es-module-loader
- 通过模块引入到页面中,或者去npm的cdn (opens new window)去找cdn文件,直接引入
<body>
<script src="https://unpkg.com/promise-polyfill@8.2.0/dist/polyfill.min.js" nomodule></script>
<script src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js" nomodule></script>
<script src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js" nomodule></script>
</body>
2
3
4
5
并且在ie中还需要引入promise的Polyfill,因为ie不支持promise
其实其工作原理就是将不识别的语法交给babel去转换,将不支持的文件通过请求拿过来再转换一次
当然,在支持的ES Modules
浏览器中会重复执行,可以使用nomodule
属性解决,这个属性会让脚步只在不支持的ES Modules的浏览器中工作
当然这种方式只能在开发阶段用,不能在生成环境使用,因为它是运行阶段动态解析脚本,性能会非常差,真正的生产环境还是要预先编译成ES5