16.7 在浏览器中使用 ES6 模块
让我们看一下浏览器是如何支持 ES6 模块的。
浏览器对于 ES6 模块的支持工作正在进行
类似于模块加载,浏览器中对于模块其它方面的支持也正在进行中。所有你在此处读到的内容都可能改变。
16.7.1 浏览器:异步模块对比同步脚本
在浏览器中,有两种不同的实体:脚本和模块。它们有略微不同的语法,行为也不一样。
下面是一个不同点的概览,后面会详细讲述:
脚本 | 模块 | |
---|---|---|
HTML 元素 | <script> | <script type="module"> |
顶级的变量是 | 全局变量( global ) | 模块的本地变量 |
顶级的 this 值 |
window | undefined |
执行 | 同步地 | 异步地 |
声明式地引入( 引入语句 ) | 否 | 是 |
编程式地引入( 基于 Promise 的 API ) | 是 | 是 |
文件扩展名 | .js | .js |
16.7.1.1 Script
Script 是浏览器中传统的嵌入 JavaScript 脚本和引用外部 JavaScript 文件的方式。 Script 有一个网络媒体类型属性,该属性用作:
- web 服务器传回来的 JavaScript 文件的内容类型。
- <script> 元素的
type
属性值。注意,对于 HTML5 ,如果 <script> 元素包含或者引用的是 JavaScript ,则推荐省略type
属性。
下面是最重要的取值:
text/javascript
:是一个遗留的值,如果省略 script 标签的type
属性,默认值就是它。对于 Internet Explorer 8 和更早版本的浏览器来说,这是最安全的选择。application/javascript
:现代浏览器中推荐使用。
JavaScript 线程在代码加载并执行完之后停止。
16.7.1.2 模块
为了与 JavaScript 通常的运行完整性语义保持一致,模块体的执行不能被打断。这给导入模块留下了两个选择:
- 1、在模块体执行的过程当中,同步加载指定的外部模块。这就是 Node.js 的方式。
- 2、在模块体执行之前,异步地加载指定的所有模块。这就是 AMD 模块处理的方式,是浏览器环境的最佳选择,因为模块通过互联网加载,在模块加载的时候没有必要停止代码的执行。另一个好处是,这种方式允许并行地加载多个模块。
ECMAScript 6 提供了两种场景下都是最好的方式:Node.js 中的同步语法加上 AMD 的异步加载。 ES6 模块在语法上比 Node.js 模块简单:引入和导出必须要在顶级范围使用,这也意味着不能根据条件来引入。这种约束使 ES6 模块加载器能够静态分析当前模块引入了些什么模块,并在当前模块体执行之前加载好需要的模块。
脚本同步的本质使它们不能成为模块。脚本甚至不能声明式地引入模块(如果想引入模块,只能使用可动态编程编程引入的模块加载器 API )。
在浏览器中,模块可以通过新的 <script> 元素形式使用,这种方式是完全异步的:
<script type="module">
import $ from 'lib/jquery';
var x = 123;
// The current scope is not global
console.log('$' in window); // false
console.log('x' in window); // false
// `this` still refers to the global object
console.log(this === window); // true
</script>
正如你看见的一样, script 元素有自己的作用域,在里面的变量是该作用域的本地变量。注意,模块代码默认是处于严格模式下的。这是一个好消息 - 不用再写 'use strict'
了。
类似于普通的 <script> 元素, <script> 也可用于加载外部的模块。例如,下面的标签通过 main
模块启动 web 应用( import
属性是我发明的,现在还不清楚会用什么名字)。
<script type="module" import="impl/main"></script>
在 HTML 中通过自定义 <script> 类型来支持模块的优点是:在老的浏览器中可以很容易地通过 polyfill (一种库)来引入这种支持。最终可能是也可能不是一个专用的模块元素(例如 module
)。
16.7.2 打包
现代的 web 应用由很多通常比较小的模块组成。通过 HTTP 加载这些模块会带来性能上的负面影响,因为每一个模块都需要一个独立的请求。因此,在 web 开发世界里,将多个模块放在一个文件中是一种运用很久的传统做法。当前的方法很复杂,容易出错,并且仅对 JavaScript 有效。有两种解决办法:
- HTTP/2 :将允许在一个 TCP 连接中执行多个请求,这使打包显得不是那么必要了。然后可以增量地更新应用,因为如果某一个模块改变了,浏览器没必要重新下载整个打包文件。
- 包:另外, W3C 技术架构组正在制定一份用于“在 Web 环境上打包”的规范。想法是将整个文件夹放在一个包里面(想一下 ZIP 文件,但是是一种不同的格式)。然后这种包 URL :
http://example.org/downloads/editor.pack#url=/root.html;fragment=colophon
等价于下面这种普通的 URL ,如果这个包在服务器的根路径下打开的话:
http://example.org/root.html#colophon
浏览器必须确保一旦你在一个包里面,那么相对 URL 就要能按照预期工作。
本节来源
《 Modules: Status Update 》, David Herman 的幻灯片。
《 Modules vs Scripts 》,David Herman 的一封邮件。