企业网站,个人博客等WordPress网站以及其他语言网站开发定制需求加QQ详聊。

如何理解ES6模块

JavaScript笔记 码云 108℃ 0评论
目录
[隐藏]

这篇文章主要探索了ES6模块,展示了它们如何在当前的转译器(transpiler)的帮助下使用。

如何理解ES6模块

几乎每一种语言都有一个模块的概念——一种将在一个文件中声明的功能包含在另一个文件中的方法。通常,开发人员创建一个封装好的代码库,负责处理相关任务。应用程序或其他模块可以引用该库。

好处是:

  1. 代码可以被分割成更小的自包含功能文件。
  2. 相同的模块可以在任意数量的应用程序之间共享。
  3. 理想情况下,模块永远不需要由其他开发人员检查,因为它们已经被证明是有效的。
  4. 引用模块的代码理解它是一个依赖关系。如果模块文件被更改或移动,问题就会立即显现出来。
  5. 模块代码(通常)有助于消除命名冲突。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个独立脚本,但这不是一个实用解决方案:

  1. 每个脚本启动一个新的HTTP请求,这会影响页面性能。HTTP/2在一定程度上缓解了这个问题,但它不会帮助其他领域的脚本,比如CDN。
  2. 每个脚本在运行时都会停止进一步的处理。
  3. 依赖关系管理是一个手动过程。在上面的代码中,如果lib1.js在lib2.js中引用代码,代码将失败,因为它没有加载。这可能会中断JavaScript的进一步处理。
  4. 除非使用适当的模块模式,否则函数可以重写其他函数。早期的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模块

喜欢 (5)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(2)个小伙伴在吐槽
  1. 楼主下次来个es6的教程
    天桥下的等待2018-11-26 22:35 回复
  2. 学习了
    儒家2018-11-24 10:29 回复