美文网首页
Understanding ES6 Modules(译)

Understanding ES6 Modules(译)

作者: _志铭 | 来源:发表于2021-04-18 20:53 被阅读0次

    原文地址

    这篇文章探索ES6 modules,在编译器的帮助下展示他们如何被使用到今天

    几乎每种语言都有模块的概念- 一种在另一个文件中包含功能性的声明的文件。一般的,开发者创建一个封装好的表示处理相关任务的代码库。这个库可以被其他应用或者其他模块引用。

    模块的益处:

    1. 代码可以被分割成更小的独立功能文件
    2. 同一个模块可以被多个应用共享
    3. 理想情况,模块不需要其他开发者检查其可用性,因为他们已经被证实可以运行。
    4. 代码里引用一个模块可以理解这是一个依赖。如果这个模块文件修改了或者移动了,问题会立马显现。
    5. 模块代码通常帮助我们彻底根除命名冲突。module1中的x()函数不会与modules2中的x()函数发生冲突。使用了类似命名空间的选项,因此调用变成了module1.x()和module2.x().

    javascript的模块在哪里

    几年前开始web开发的人会惊奇的发现在JavaScript中没有模块的概念。在另一个js文件中是不可能直接引用或者包含一个JavaScript文件的。因此,开发人员寻找其他选择。

    多个html<script>标签

    html可以通过多个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年,平均每个网页使用了25个分割的scripts,但这不是一个切实的解决方案:

    • 每个脚本会发起一个新的HTTP请求,这会影响页面的性能。HTTP/2在某种程度上缓解了这个问题,但这对其他域(例如CDN)上引用的脚本无济于事
    • 每个脚本在运行时都会阻塞进一步的处理
    • 依赖管理是一个人工处理的过程.在代码中,如果lib1.js引用lib2.js的代码,这部分代码可能出错,因为它没有加载。这有可能损害深层次的JavaScript程序。
    • 函数可能覆盖其他函数,除非使用合适的模块模式。早期的JavaScript库因使用全局函数名称或覆盖原生方法而臭名昭著。

    脚本级联

    解决多个<script>标记问题的一种解决方案是将所有JavaScript文件连接为一个大文件。这解决了一些性能和依赖性管理问题,但可能会导致手动构建和测试步骤

    模块装载机

    诸如RequireJSSystemJS之类的系统提供了一个库,用于在运行时加载和命名其他JavaScript库。 必要时,使用Ajax方法加载模块。该系统可以提供帮助,但是对于较大的代码库或将标准<script>标记添加到组合中的站点,可能会变得复杂

    模块捆绑器,预处理器和编译器

    捆绑软件引入了一个编译步骤,以便在构建时生成JavaScript代码。处理代码以包括依赖关系,并生成单个ES5跨浏览器兼容的串联文件。流行的选项包括BabelBrowserifywebpack和更多常规任务像GruntGulp之类的跑步者。

    JavaScript构建过程需要一些努力,但是有很多好处:

    • 处理是自动化的,因此减少了人为错误的可能性。
    • 进一步的处理可能会减少代码,删除调试命令,缩小生成的文件等。
    • Transpiling允许您使用其他语法,例如TypeScriptCoffeeScript

    ES6模块

    上面的选项引入了各种竞争的模块定义格式。广泛采用的语法包括:

    • CommonJS — module.exports,并要求在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
    

    可以为导入使用别名来解决命名冲突:

    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模块

    在撰写本文时,在基于Chromium的浏览器(v63 +),Safari 11+和Edge 16+中,ES6模块受支持。 Firefox支持将在60版中提供(它位于v58 +中的about:config标志的后面)。

    必须通过在\ <script >标记中设置type =“ module”属性来加载使用模块的脚本。例如:

    <script type="module" src="./main.js"></script>
    

    或内联:

    <script type="module">
      import { something } from './somewhere.js';
      // ...
    </script>
    

    无论模块在页面或其他模块中被引用多少次,模块都会被解析一次。

    服务器注意事项

    模块必须使用MIME类型application/javascript提供服务。大多数服务器将自动执行此操作,但对动态生成的脚本或.mjs文件保持警惕(请参阅下面的Node.js部分)。

    常规<script>标记可以获取其他域上的脚本,但是使用跨域资源共享(CORS)来获取模块。因此,不同域上的模块必须设置适当的HTTP标头,例如Access-Control-Allow-Origin:*。

    最后,除非将crossorigin =“ use-credentials”属性添加到\ <script >标记,并且响应中包含标头Access-Control-Allow-Credentials:true,否则模块将不会发送Cookie或其他标头凭据。

    模块执行被推迟

    <script defer >属性将延迟脚本执行,直到文档已加载和解析为止。默认情况下,模块(包括嵌入式脚本)会延迟。例子:

    <!-运行第二个->
    <script type ="module">
      // do something...
    </script>
    
    <!-运行第三个->
    <script defer src ="c.js"> </script>
    
    <!-首先运行->
    <script src ="a.js"> </script>
    
    <!-运行第四个->
    <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,这意味着可以开发Node软件包管理器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才能完全实现。 语法,它们以根本不同的方式工作:

    • 在执行代码之前,已预先解析了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。 这是一个简单的选项,可消除大部分复杂性,并有助于代码编辑器和lint。

    是否应该在Node.js中使用ES6模块?

    ES6模块仅适用于Node.js v10及更高版本(于2018年4月发布)。 转换现有项目不太可能带来任何好处,并且会使应用程序与Node.js的早期版本不兼容。

    对于新项目,ES6模块提供了CommonJS的替代方法。 语法与客户端编码相同,并且可以提供更容易的同构JavaScript路由,该JavaScript可在浏览器或服务器上运行。

    模块竞争

    标准化的JavaScript模块系统花了很多年才能实现,甚至花了更长的时间才能实现,但问题已得到纠正。从2018年中期开始,所有主流浏览器和Node.js都支持ES6模块,尽管在每个人升级时都可能会有切换滞后的情况。

    立即学习ES6模块,以使您明天的JavaScript开发受益。

    相关文章

      网友评论

          本文标题:Understanding ES6 Modules(译)

          本文链接:https://www.haomeiwen.com/subject/wmgvlltx.html