一:什么是模块?
模块仅仅是一个文件,一个脚本而已。用一些关键字比如 export 和 import 来交换模块之间的功能(functionality)或者从一个模块中调用另一个模块中的函数
二:模块相较于普通的脚本有什么区别呢?
-
模块始终默认使用使用
use strict
,例如,对一个未声明的变量赋值将会抛出错误
<script type="module">
a = 5; // error
</script>
- 每个模块都有自己的顶级作用域(top-level scope)。换句话说,一个模块中的顶级作用域变量和函数在其他脚本中是不可见的。
- 代码仅在第一次导入时解析,如果将一个模块导入到多个其他位置,则仅在第一次导入时解析其代码,然后将导出提供给所有导入的位置。
三:构建工具
- 在日常开发中,浏览器模块很少以原始形式使用,通常,我们用一些特殊工具,像 Webpack,将他们打包在一起,然后部署到服务器。
- 使用打包工具的一个好处是——它们对于如何解析模块给与了足够多的控制,比如允许使用裸模块,以及 CSS/HTML 模块等等。
- 这里列出了一些构建工具做的事情:
- 从一个打算放在 HTML 中的
<script type="module">
主模块开始。 - 分析它的依赖:它的导入以及它的导入的导入等。
- 用打包函数替换掉原生的
import
调用,生成一个(或者多个,这是可调的)具有所有模块的文件,这就是打包工具的工作。特殊的模块类型,比如 HTML/CSS 模块也是可以这样做的。 - 在这个过程中,可能会应用其他的转换或者优化:
* 删除无法访问的代码
* 删除未使用的导出(“tree-shaking”)
* 删除开发中使用的如console
和debugger
这样的语句
* 使用 Babel 可以将现代的,前沿的 JavaScript 语法转换为具有类似功能的旧语法
* 最终生成压缩文件(删除无用空格,变量用短的名字替换等)
也就是说,原生模块也是可以使用的。
- 从一个打算放在 HTML 中的
四:模块的导入与导出
(一)静态导入 image.png- 命名/导出与默认导出 image.png
-
导入导出总结
image.png
我们把导入/导出语句放在脚本的顶部或者底部都是没问题的。
(二)动态导入- 如何动态的按需导入模块呢?
import()函数 -
import(module)
函数可以在任何地方调用。它返回一个解析为模块对象的 promise。
- 如何动态的按需导入模块呢?
let modulePath = './Module.js';
import(modulePath)
.then(obj => console.log('load ok'))
.catch(err => console.log('err'))
另外,如果在一个 async 函数里,我们可以这样使用let module = await import(modulePath)
。
使用模式如下:
<!doctype html>
<script>
async function load() {
let say = await import('./say.js');
say.hi(); // Hello!
say.bye(); // Bye!
say.default(); // Module loaded (export default)!
}
</script>
<button onclick="load()">Click me</button>
四:总结
下面总结一下模块的核心概念:
- 模块就是文件。浏览器需要使用
<script type="module">
属性以使import/export
可用,这里有几点差别:- 默认是延迟解析的
- 行内脚本是异步的
- 加载外部不同源(domain/protocol/port)脚本时,必须提供 CORS 响应头
- 重复的外部脚本会被忽略
- 模块有自己的本地顶级作用域,可以通过
import/export
交换功能 - 模块始终使用
use strict
- 模块代码只执行一次。导出的代码创建一次然后会在各导入之间共享
所以,通常来说,当我们使用模块的时候,每个模块实现特定功能并导出它。然后我们需要它的时候直接使用 import
导入即可。浏览器会自动加载和解析脚本。
在生产环境中,开发者经常基于性能或者其他原因而使用诸如 Webpack 这类的打包工具。
网友评论