如何理解ES6模块
这篇文章主要探索了ES6 模块,展示了它们如何在当前的转译器(transpiler)的帮助下使用。
几乎每一种语言都有一个模块的概念——一种将在一个文件中声明的功能包含在另一个文件中的方法。通常,开发人员创建一个封装好的代码库,负责处理相关任务。应用程序或其他模块可以引用该库。
好处是:
- 代码可以被分割成更小的自包含功能文件。
- 相同的模块可以在任意数量的应用程序之间共享。
- 理想情况下,模块永远不需要由其他开发人员检查,因为它们已经被证明是有效的。
- 引用模块的代码理解它是一个依赖关系。如果模块文件被更改或移动,问题就会立即显现出来。
- 模块代码(通常)有助于消除命名冲突。module1 中的 Functionx()不能与 module2 中的 Functionx()冲突。使用了名称空间之类的选项,因此调用 becomemodule1.x()和 module2.x()。
JavaScript 中的模块在哪里?
几年前开始 web 开发的人都会震惊地发现 JavaScript 中没有模块的概念。不可能直接引用或将一个 JavaScript 文件包含在另一个文件中。因此,开发人员诉诸于替代方案。
多个 HTML <script>标签
HTML 可以使用多个<script>标签加载任何数量的 JavaScript 文件
<script src="lib1.js"></script> <script src="lib2.js"></script> <script src="core.js"></script> <script> console.log('inline code'); </script>
在 2018 年 web 页面平均使用 25 个独立脚本,但这不是一个实用解决方案:
- 每个脚本启动一个新的 HTTP 请求,这会影响页面性能。HTTP/2 在一定程度上缓解了这个问题,但它不会帮助其他领域的脚本,比如 CDN。
- 每个脚本在运行时都会停止进一步的处理。
- 依赖关系管理是一个手动过程。在上面的代码中,如果 lib1.js 在 lib2.js 中引用代码,代码将失败,因为它没有加载。这可能会中断 JavaScript 的进一步处理。
- 除非使用适当的模块模式,否则函数可以重写其他函数。早期的 JavaScript 库使用全局函数名称或覆盖本地方法。
脚本连接
解决一个问题的多个<脚本>标记是所有 JavaScript 文件合并到一个大文件。这解决了一些性能和依赖管理问题,但可能需要手动构建和测试步骤。
模块加载器
RequireJS 和 SystemJS 等系统提供一个库,用于在运行时加载和命名空间的其他 JavaScript 库。当需要时,使用 Ajax 方法加载模块。这些系统是有帮助的,但可能成为更大的复杂代码库或网站将标准<script>标记添加到混合模式中。
模块捆绑器,预处理器和编码器
绑定器引入一个编译步骤,以便在构建时生成 JavaScript 代码。代码被处理了包括依赖和生成一个 ES5 跨浏览器兼容的连接文件。流行的选项包括 Babel、Browserify、webpack 和诸如 Grunt 和 Gulp 等更一般的任务运行程序。
JavaScript 构建过程需要一些努力,但是有一些好处:
- 处理过程是自动化的,所以人为错误的可能性更小。
- 进一步的处理可以剥离代码,删除调试命令,缩小结果文件等。
- 转换允许您使用替代的语法,例如 TypeScript 或 CoffeeScript。
ES6 模块
上面的选项引入了各种相互竞争的模块定义格式。广泛适用的语法包括:
- CommonJS -module.exports 导出接口和 require 引入模块是在 Node.js 中使用的语法
- 异步模块定义(AMD)
- 通用模块定义(UMD)。
因此,在 ES6(ES2015)中提出了单一的本地模块标准。
ES6 模块中的所有内容默认都是私有的,并且在严格模式下运行(不需要‘使用严格’)。使用 export 导出公共变量、函数和类。例如:
// lib.js export const PI = 3.1415926; export function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } export function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // private function function log(...msg) { console.log(...msg); }
或者,可以使用单个导出语句。例如:
// lib.js const PI = 3.1415926; function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // private function function log(...msg) { console.log(...msg); } export { PI, sum, mult };
然后使用 import 将项目从一个模块中提取到另一个脚本或模块中:
// main.js import { sum } from './lib.js'; console.log( sum(1,2,3,4) ); // 10
在这种情况下,lib.js 与 main.js 在同一个文件夹中。绝对的文件引用(/),相对的文件引用(./或../)或者是完整的 url 可以被使用。
可同时导入多个项目:
import { sum, mult } from './lib.js'; console.log( sum(1,2,3,4) ); // 10 console.log( mult(1,2,3,4) ); // 24
并且可以对 imports 进行别名化以解决命名冲突:
import { sum as addAll, mult as multiplyAll } from './lib.js'; console.log( addAll(1,2,3,4) ); // 10 console.log( multiplyAll(1,2,3,4) ); // 24
最后,所有公共项目都可以通过提供一个命名空间来导入:
import * as lib from './lib.js'; console.log( lib.PI ); // 3.1415926 console.log( lib.add(1,2,3,4) ); // 10 console.log( lib.mult(1,2,3,4) ); // 24
在浏览器中使用 ES6 模块
在编写本文时,基于 chrome 的浏览器(v63+)、Safari 11+和 Edge 16+支持 ES6 模块。Firefox 支持将在版本 60 中发布(它在 v58+中的 about:config 标志后面)。
脚本使用必须通过设置一个 type=”module”属性的<script>标记来加载模块。比如:
<script type="module" src="./main.js"></script>
或内联:
<script type="module"> import { something } from './somewhere.js'; // ... </script>
不管在页面或其他模块中引用了多少次,都只解析一次模块。
服务器方面的考虑
模块必须提供带有 MIME 类型 application/javascrip。大多数服务器都将自动执行此操作,但要注意动态生成的脚本或.mjs 文件
常规<script>标签可以在其他领域获取脚本但模块获取使用跨源资源共享(CORS)。因此,不同域上的模块必须设置适当的 HTTP 头,例如 Access-Control-Allow-Origin: *
最后,模块不会发送 cookie 或其他头凭证,除非 crossorigin=”use-credentials”属性添加到<script>标签和响应包含头 Access-Control-Allow-Credentials:true。
模块执行延迟
<script defer>属性延迟脚本执行,直到文件加载并解析。模块—包括内联脚本—默认延迟。例子:
<!-- runs SECOND --> <script type="module"> // do something... </script> <!-- runs THIRD --> <script defer src="c.js"></script> <!-- runs FIRST --> <script src="a.js"></script> <!-- runs FOURTH --> <script type="module" src="b.js"></script>
模块回退
没有模块支持的浏览器不会运行 type=”module”脚本。回退脚本可以提供 nomodule 属性,模块兼容的浏览器会忽略这个属性。例如:
<script type="module" src="runs-if-module-supported.js"></script> <script nomodule src="runs-if-module-not-supported.js"></script>
应该在浏览器中使用模块吗?
浏览器支持正在发展,但它可能有点过早转向 ES6 模块。目前,最好使用模块绑定器创建一个在任何地方都可以工作的脚本。
在 Node.js 中使用 ES6 模块
node.js 在 2009 年发布的时候,任何运行时不提供模块都是不可想象的。采用了 CommonJS,这意味着可以开发节点包管理器 npm。从那时起,使用率呈指数级增长。
CommonJS 模块的编码方式与 ES2015 模块类似。
module.exports 被使用而不是 export:
// lib.js const PI = 3.1415926; function sum(...args) { log('sum', args); return args.reduce((num, tot) => tot + num); } function mult(...args) { log('mult', args); return args.reduce((num, tot) => tot * num); } // private function function log(...msg) { console.log(...msg); } module.exports = { PI, sum, mult };
require(而不是 import)用于将此模块放入另一个脚本或模块:
const { sum, mult } = require('./lib.js'); console.log( sum(1,2,3,4) ); // 10 console.log( mult(1,2,3,4) ); // 24
require 还可以导入所有项目:
const lib = require('./lib.js'); console.log( lib.PI ); // 3.1415926 console.log( lib.add(1,2,3,4) ); // 10 console.log( lib.mult(1,2,3,4) ); // 24
因此,ES6 模块易于在 Node.js 中实现吗?嗯,没有。
ES6 模块在 node.js 9.8.0+后面是一个标志,至少要到第 10 版才能完全实现。虽然 CommonJS 和 ES6 模块具有相似的语法,但它们的工作方式却截然不同:
- ES6 模块被预先解析,以便在执行代码之前进一步解析导入。
- CommonJS 模块在执行代码时根据需要加载依赖项。
在上述示例中没有差别,但应考虑以下 ES2015 模块代码:
// ES2015 modules // --------------------------------- // one.js console.log('running one.js'); import { hello } from './two.js'; console.log(hello); // --------------------------------- // two.js console.log('running two.js'); export const hello = 'Hello from two.js';
ES2015 输出结果:
running two.js running one.js hello from two.js
使用 CommonJS 编写的类似代码:
// CommonJS modules // --------------------------------- // one.js console.log('running one.js'); const hello = require('./two.js'); console.log(hello); // --------------------------------- // two.js console.log('running two.js'); module.exports = 'Hello from two.js';
CommonJS 输出结果:
running one.js running two.js hello from two.js
在某些应用程序中,执行顺序可能非常关键,如果 ES2015 和CommonJS 模块混合在同一个文件中会发生什么?要解决此问题,Node.js 只允许在文件中使用扩展名为.mjs 的 ES6 模块。扩展名为.js 的文件将默认为 CommonJS。这是一个简单的选项,它消除了许多复杂性,有助于代码编辑和打印。
你应该在 Node.js 中使用 ES6 模块吗?
ES6 模块从 Node.js v10 以上(2018 年 4 月发布)开始使用的。转换现有项目不太可能带来任何好处,而且会使应用程序与早期版本的 Node.js 不兼容。
对于新项目,ES6 模块提供了 CommonJS 的替代方案。语法与客户端编码相同,并且可能提供更容易的同构 JavaScript 路由,它可以在浏览器或服务器上运行。
一个标准化的 JavaScript 模块系统需要多年的时间才能实现,甚至需要更长的时间才能实现,但是问题已经得到了解决。尽管所有人都在升级的时候,应该会有一种切换的延迟,所有主流浏览器和 node.js 从 2018 年中期到现在都支持 ES6 模块。
码云笔记 » 如何理解ES6模块
楼主下次来个es6的教程
学习了