什么是模块

  • 一个模块就是一个文件,一个脚本就是一个模块
  • 模块之间可以相互加载
  1. export 关键字标记了可以从当前模块外部访问的变量和函数。
  2. import 关键字允许从其他模块导入功能。
1
2
3
4
// 📁 sayHi.js
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
1
2
3
4
5
6
7
// 📁 main.html
<!doctype html>
<script type="module">
import {sayHi} from './say.js';

document.body.innerHTML = sayHi('John');
</script>

在这里插入图片描述


核心模块功能

  • 始终使用”use strict”
  • 模块级作用域:每个模块都有自己独立的作用域
    hello.js 尝试使用在 user.js 中声明的变量 user,失败了:
    1
    2
    3
    4
    #index.html
    <!doctype html>
    <script type="module" src="user.js"></script>
    <script type="module" src="hello.js"></script>
    1
    2
    //user.js
    let user = "John";
    1
    2
    //hello
    alert(user); // no such variable (each module has independent variables)
  • 在浏览器中,每个 <script type="module"> 也存在独立的顶级作用域
    1
    2
    3
    4
    5
    6
    7
    8
    <script type="module">
    // 变量仅在这个 module script 内可见
    let user = "John";
    </script>

    <script type="module">
    alert(user); // Error: user is not defined
    </script>
  • 模块代码仅在第一次导入时被解析
    如果在某个位置修改了导入的代码,那么其它的模块也可以看到就这个修改(包含源文件)
1
2
// 📁 alert.js
alert("Module is evaluated!");
1
2
3
4
5
6
7
// 在不同的文件中导入相同的模块

// 📁 1.js
import `./alert.js`; // Module is evaluated!

// 📁 2.js
import `./alert.js`; // (什么都不显示)
  • import.meta 对象包含关于当前模块的信息
    它的内容取决于其所在的环境。在浏览器环境中,它包含当前脚本的 URL,或者如果它是在 HTML 中的话,则包含当前页面的 URL。
1
2
3
<script type="module">
alert(import.meta.url); // 脚本的 URL(对于内嵌脚本来说,则是当前 HTML 页面的 URL)
</script>
  • 在一个模块中,”this”是 undefined
    1
    2
    3
    4
    5
    6
    7
    <script>
    alert(this); // window
    </script>

    <script type="module">
    alert(this); // undefined
    </script>

浏览器特定功能

  • 模块脚本总是延迟的
  1. 下载外部模块脚本 <script type="module" src="..."> 不会阻塞 HTML 的处理,它们会与其他资源并行加载。
  2. 模块脚本会等到 HTML 文档完全准备就绪(即使它们很小并且比 HTML 加载速度更快),然后才会运行。
  3. 保持脚本的相对顺序:在文档中排在前面的脚本先执行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 第二个脚本会先于第一个脚本执行
    <script type="module">
    alert(typeof button); // object:脚本可以“看见”下面的 button
    // 因为模块是被延迟的(deferred,所以模块脚本会在整个页面加载完成后才运行
    </script>

    // 相较于下面这个常规脚本:

    <script>
    alert(typeof button); // button 为 undefined,脚本看不到下面的元素
    // 常规脚本会立即运行,常规脚本的运行是在在处理页面的其余部分之前进行的
    </script>

    <button id="button">Button</button>
  • Async 适用于内联脚本
    1
    2
    3
    4
    5
    6
    7
    <!-- 所有依赖都获取完成(analytics.js)然后脚本开始运行 -->
    <!-- 不会等待 HTML 文档或者其他 <script> 标签 -->
    <script async type="module">
    import {counter} from './analytics.js';

    counter.count();
    </script>
  • 外部脚本
  1. 具有相同 src 的外部脚本仅运行一次
1
2
3
4
<!-- 脚本 my.js 被加载完成(fetched)并只被运行一次 -->
<script type="module" src="my.js"></script>
<script type="module" src="my.js"></script>

  1. 从另一个源(例如另一个网站)获取的外部脚本需要 CORS header,如我们在 Fetch:跨源请求 一章中所讲的那样。换句话说,如果一个模块脚本是从另一个源获取的,则远程服务器必须提供表示允许获取的 header Access-Control-Allow-Origin
1
2
3
<!-- another-site.com 必须提供 Access-Control-Allow-Origin -->
<!-- 否则,脚本将无法执行 -->
<script type="module" src="http://another-site.com/their.js"></script>
  • 不允许裸模块
    在浏览器中,import 必须给出相对或绝对的 URL 路径
    1
    2
    import {sayHi} from 'sayHi'; // Error,“裸”模块
    // 模块必须有一个路径,例如 './sayHi.js' 或者其他任何路径
  • 兼容性
1
2
3
4
5
6
7
8
<script type="module">
alert("Runs in modern browsers");
</script>

<script nomodule>
alert("Modern browsers know both type=module and nomodule, so skip this")
alert("Old browsers ignore script with unknown type=module, but execute this.");
</script>