了解JavaScript中的ES模块

开发 前端
模块在每个编程语言中都能找到。它是一种能在一个代码块中引入另一个代码的模块功能的方法。这些模块是开发人员开发的具有特定功能的代码,可以在项目其他地方重复使用。模块化为你提供了一些好处,比如代码的可复用性和模块化。

[[332921]]

本文转载自微信公众号「新钛云服」,作者方章和 翻译 。转载本文请联系新钛云服公众号。

模块在每个编程语言中都能找到。它是一种能在一个代码块中引入另一个代码的模块功能的方法。这些模块是开发人员开发的具有特定功能的代码,可以在项目其他地方重复使用。模块化为你提供了一些好处,比如代码的可复用性和模块化。

如果你之前一直在使用JavaScript开发代码,你就会知道早期的JavaScript没有这样的模块功能。开发者为了将js文件加载到他们的页面中,不得不使用HTML <script>标签。直到后来,几种模块定义规范才开始出现。

 

  • CommonJS — Node.js中使用的module.export和require语法
  • Asynchronous Module Definition (AMD)
  • Universal Module Definition (UMD)
  • ES Modules

 

 

首先,我们先来看下为什么需要这些模块化定义的规范

为什么我们需要模块化

每当你思考程序是如何工作的,其实它们所做的就是变量管理。它为变量赋值,修改变量,将两个变量组合在一起等等。但是当变量的数量随着你的应用程序的规模而增加时,你会发现你的代码管理起来会非常麻烦。

解决这个问题的方法是只需要考虑几个变量。JavaScript实现这一目标的方式被称为作用域。由于JavaScript中的作用域定义方式导致函数不能访问其他函数中定义的变量。虽然这使得你的变量不能被其他函数访问,但这又引起了另一个问题--你很难在不同函数之间共享变量。解决这个问题就是将它们定义在全局作用域上。

虽然这种方法可以解决问题,但并不推荐。你的脚本标签应该按照正确的顺序,你必须确保没有人改变这个顺序。如果顺序确实发生了变化,你的应用程序将抛出一个错误。这使得代码管理变得很棘手。你永远不知道什么会破坏什么。任何函数都可以访问,所以你不知道哪些函数依赖于哪些脚本。

另一个问题是,在该全局范围内的每一部分代码都可以被其它代码改变。这将允许恶意的和非恶意的代码访问甚至修改你的全局变量,无论是否有恶意。

所以需要引入模块来帮助克服这些问题。

模块化是如何优雅的解决这个问题的

模块可以让你更好地组织和管理变量和函数。通常情况下,属于同一功能的函数和变量会被放在一个模块中。这就把这些变量放到了模块作用域中。模块作用域可以用来在模块中的函数之间共享变量。

这也使得变量也可以为其他模块所用。他们可以明确的说哪些变量、类或函数应该对外部模块可用。这就是所谓的导出。一旦你有了一个导出,其他模块就可以明确地说它们依赖于该变量、类或函数。由于这种明确的关系,你将知道如果你删除一个模块,哪些模块将被破坏。

一旦你能够导入和导出变量和函数,你就可以更容易地将你的代码分割和分解成可以独立工作的代码块。你以后可以通过使用这些模块来构建你的应用程序,类似于用乐高积木来构建。

为了实现这个超级有用的功能,已经多次尝试用JavaScript添加模块功能。

现有的模块化系统

CommonJS

CommonJS是NodeJS一直在使用的。使用Node,你会得到CommonJS的module.exports和require并可以直接使用。但是,与Node不同的是,浏览器并不支持CommonJS。此外,CommonJS会同步加载模块,因此对于浏览器来说,它不是一个最佳的解决方案。你可以使用Webpack或Browserify等打包程序来解决这个问题。

  1. //   filename: bar.js 
  2.  
  3. //   dependencies 
  4. var $ = require('jquery'); 
  5.  
  6. //   methods 
  7. function myFunction(){}; 
  8.  
  9. //   exposed public method (single) 
  10. module.exports = myFunction; 

Asynchronous Module Definition (AMD)

AMD诞生于一群不喜欢CommonJS发展方向的开发者。事实上,AMD在发展初期就从CommonJS中分裂出来了。AMD和CommonJS的主要区别在于AMD是异步加载模块的。这在浏览器中非常受欢迎,因为启动时间对于良好的用户体验至关重要。

  1. //   filename: bar.js 
  2. define(['jquery'], function ($) { 
  3.    //   methods 
  4.    function myFunction(){}; 
  5.  
  6.    //   exposed public methods 
  7.    return myFunction; 
  8. }); 

由于CommonJS和AMD在各自的领域相当流行,所以需要一个 "通用 "的模式来支持两种风格。但事实证明,UMD又乱又丑。虽然它确实同时支持AMD和CommonJS,也支持老式的 "全局 "变量定义。

Universal Module Definition (UMD)

什么是ES模块

众所周知,JavaScript缺乏一个标准模块定义规范。因此,在ES6中提出了一个单一的、原生的模块标准。import和export指令允许程序在不运行代码的情况下导入导出,从而建立一个完整的模块依赖关系。

其语法格式还是简单好用的,并且兼容浏览器中的同步和异步操作模式。ES模块在浏览器中很快就可以使用,但在Node.js中,要想出一个向后兼容并能实现增量升级的解决方案就有点难了。在Node.js中,原生的ES模块在实验性模块标志后面长期可用。

下面以ES6模块为例。

JavaScript

  1. //------ library.js ------ 
  2. export const sqrt = Math.sqrt; 
  3. export function square(x) { 
  4.    return x * x; 
  5. export function diagonal(x, y) { 
  6.    return sqrt(square(x) + square(y)); 
  7. //------ main.js ------ 
  8. import { square, diagonal } from 'library'
  9. console.log(square(13)); // 169 
  10. console.log(diagonal(12, 5)); // 13 
  11. const app = document.getElementById("app"); 
  12. app.innerHTML = "<h1>Demo App for ES Modules</h1>"
  13. const input = document.getElementById("num"); 
  14. input.addEventListener("change",displaySquare); 
  15. function displaySquare(){ 
  16. var sqrOutput = document.getElementById("sqr"); 
  17. sqrOutput.value = square(input.value); 

HTML

  1. <HTML> 
  2.    <head> 
  3.        <title>ES Modules Demo</title> 
  4.    </head> 
  5.    <body> 
  6.        <script type="module" src="./main.js" ></script> 
  7.        <div id="app"></div> 
  8.        <label>Input</label> 
  9.        <input id="num" type="number" placeholder="Enter number here"/> 
  10.       <br> 
  11.       <label>Output</label> 
  12.        <input id="sqr" type="number" disabled style="margin- top:20px"/> 
  13.    </body> 
  14. </HTML> 

如上图所示,在HTML文件中,你需要在脚本标签中指定type="模块",浏览器才会将其视为ECMAScript模块。

兼容性

为了向后兼容,你可以在脚本标签中包含nomodule(其中加载的JS文件是单个打包文件)。支持ES模块的浏览器会知道忽略这一点。这个解决方案即使在最老的浏览器中也能使用。Willem的回答已经很好的解释了这个问题。

在上面的方案中,我们会在HTML中加入这样的内容。

  1. <script type="module" src="./main.js" > 
  2. </script> 
  3. <script nomodule src="./fallback.js" > 
  4. </script> 

如果你是在本地测试,你将需要在服务器上运行这个,因为你会遇到CORS问题。请在这里阅读更多信息。模块以绝对或相对引用导入,必须以"/"、"./"或"./"开头。

注意:

动态导入

最新的ES2020版本确实带有动态导入功能。要动态导入模块,导入关键字可以作为函数调用。当以这种方式使用时,它会返回一个promise。

  1. import('/modules/library.js'
  2. .then((module) => { 
  3.    // Do something with the module. 
  4. }); 
  5. //or using await 
  6. let module = await import('/modules/library.js'); 

关于es模块的详细兼容性可以参考这里 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#import以及https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#export

您是否应该选择使用ES模块?

对于浏览器来说,ES模块是新的标准。可以开箱即用的异步模块加载功能,你可以获得更快的启动时间以更好的性能。虽然您可以在浏览器中使用CommonJS与一些额外的插件,但强烈建议您切换到ES模块,因为它们是浏览器中的原生模块。

ES原生模块允许您获得单个模块的加载,而不是单个打包文件。这是相当有用的,它减少了加载数据的大小。浏览器对原生模块的兼容性也很重要,因为它决定了原生的ES模块是否会被实现,或者我们是否会回退到我们的模式,它将加载一个单一的文件。当你得到一个单一的bundle文件时,其中一个问题是,当你的应用程序变得更大时,bundle js文件的大小也会增加,从而影响启动时间和性能。你可以通过使用代码拆分来避免这个问题,这是现代打包器(如webpack)中的一个功能。但在某些情况下,我们可能会选择模块打包器,如webpack而不是ES模块。如果你有CSS、图像、字体等资产,甚至是XML、CSV等数据文件,你可能会选择webpack解决方案,因为webpack提供了文件打包功能。

你还应该考虑到浏览器对HTTP2的支持。当你使用本地模块时,你的浏览器会单独加载这些模块。但在HTTP2的帮助下,我们可以用一个连接同时服务多个请求,而不是发送多个HTTP请求。根据CanIUse的数据,96.49%的浏览器使用HTTP2。

但是当你开发一个应用程序时,即使是剩下的3.51%也应该满足,那么你可能会想改用webpack。这是因为如果你坚持使用原生的ES模块,你的应用程序将需要发送几个HTTP请求来加载每个单独的模块。

在Node中,情况就完全不同了。由于该功能仍被标记为实验性的,所以你最好坚持使用CommonJS。不过你还是可以尝试一下ES模块。你可以在这里查看上面例子的源代码。你也可以在这里查看实时演示。我希望你明白什么是ES模块,以及为什么需要它们。

 

责任编辑:武晓燕 来源: 新钛云服
相关推荐

2021-11-22 22:14:46

JavaScript开发模块

2012-07-25 13:25:11

ibmdw

2023-11-08 08:40:35

JavaScriptS 模块

2012-02-06 13:52:33

JavaScript

2017-01-20 08:30:19

JavaScriptfor循环

2016-05-11 15:36:58

BabelES7JavaScript

2020-12-23 14:18:43

JavaScript模块导出

2020-11-18 09:06:02

JavaScript开发技术

2022-08-05 13:14:25

ES2022JavaScript代码

2022-05-25 07:22:07

ES12JavaScript语言

2022-09-14 09:37:17

JavaScript默认导出

2010-09-13 09:18:22

JavaScript模块模式

2020-12-25 10:28:41

JavaScript模块module

2019-11-06 09:52:01

JavaScript单线程非阻塞

2020-10-14 11:10:52

ES2020JavaScript前端

2019-12-02 16:05:10

前端模块化JavaScript

2024-01-09 07:42:46

Shutil 模块Python 编程工具

2019-10-16 08:25:33

JavaScriptwebprototype

2017-10-09 18:21:20

JavaScriptES6ES8

2021-05-05 11:29:53

Pythonpyglet开发模块
点赞
收藏

51CTO技术栈公众号