如何理解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的教程
学习了