全面认识ECMAScript模块

2020-10-10 15:20:39 浏览数 (3103)

关于ES模块,本文将提供一些详细的解读,希望对你有所帮助!

什么是ES模块?

ECMAScript模块(简称ES模块)是2015年推出的 JavaScript 中代码重用的机制。在高度碎片化的 JavaScript 模块场景中,它终于成为了标准。

在2015年之前, JavaScript 还没有一个标准的代码重用机制。这方面曾有过很多标准化的尝试,导致这些年乱七八糟的碎片化。

你可能听说过 AMD 模块、UMD 或者 CommonJS。没有明显的赢家。终于,随着 ECMAScript 2015,ES模块登陆语言。

我们现在有了一个 "官方 "的模块系统。

ECMAScript模块无处不在?

理论上,ECMAScript 模块应该普遍适用于所有 JavaScript 环境。实际上,浏览器仍然是ES模块的主要目标。

2020年5月,Node.js v12.17.0 发货时,支持 ECMAScript 模块,没有标志。这意味着我们现在可以在 Node.js 中使用导入和导出,而无需任何额外的命令行标志。

在 ECMAScript 模块在任何 JavaScript 环境中普遍工作之前,还有很长的路要走,但方向是正确的。

ES模块是怎样的?

一个ES模块就是一个简单的文件,我们可以声明一个或多个出口。以这个虚构的 utils.js 为例。

// utils.js
export function funcA() {
  return "Hello named export!";
}


export default function funcB() {
  return "Hello default export!";
}

我们这里有两个导出。

第一个是一个命名的导出,后面是一个默认的导出,表示为导出默认。

假设我们的项目文件夹中住着这个名为 utils.js 的文件,我们可以在另一个文件中导入这个模块提供的对象。

如何从ES模块导入

假设我们在项目文件夹中还有一个名为 consumer.js 的文件。要导入 utils.js 所暴露的函数,我们可以这样做。

// consumer.js
import { funcA } from "./util.js";

这种语法是一种命名的导入方式,与命名的导出方式有异曲同工之妙。

如果要导入定义为默认导出的 funcB,我们可以这样做:

// consumer.js
import funcB from "./util.js";

如果我们想在一个文件中同时导入默认导出和命名导出,我们可以将其压缩为:

// consumer.js
import funcB, { funcA } from "./util.js";


funcB();
funcA();

我们也可以用 star 导入整个模块。

import * as myModule from "./util.js";


myModule.funcA();
myModule.default();

要注意,在这种情况下,必须显式调用默认导出。

要从远程模块导入。

import { createStore } from "https://unpkg.com/redux@4.0.5/es/redux.mjs";


const store = createStore(/* do stuff */)

浏览器中的ECMAScript模块

现代浏览器支持 ES 模块,尽管有一些注意事项。要加载一个模块,请在脚本标签的 type 属性中添加模块。








    
    ECMAScript modules in the browser




<p id="el">The result is: </p>




    import { appendResult } from "./myModule.js";


    const el = document.getElementById("el");
    appendResult(el);



这里 myModule.js 是同一个项目文件夹下的一个简单模块。

export function appendResult(element) {
  const result = Math.random();
  element.innerText += result;
}

虽然可以直接在浏览器中使用ES模块,但现在捆绑 JavaScript 应用的任务仍然是 webpack 等工具的专属,以获得最大的灵活性、代码拆分和对旧浏览器的兼容性。

动态导入

ES 模块是静态的,这意味着我们无法在运行时更改导入。有了2020年登陆的动态导入,我们可以根据用户的交互动态加载我们的代码(webpack在ECMAScript 2020中提供动态导入功能之前就已经提供了)。

考虑一个简单的 HTML,它可以加载一个脚本。








    
    Dynamic imports




<button id="btn">Load!</button>





也可以考虑用几个导出的 JavaScript 模块。

// util.js
export function funcA() {
  console.log("Hello named export!");
}


export default function funcB() {
  console.log("Hello default export!");
}

如果要动态加载这个模块,也许点击一下,我们可以这样做。

// loader.js
const btn = document.getElementById("btn");


btn.addEventListener("click", () => {
  // loads named export
  import("./util.js").then(({ funcA }) => {
    funcA();
  });
});

在这里,我们通过重构模块的对象,只加载命名的导出。

({ funcA }) => {}

ES 模块实际上就是 JavaScript 对象:我们可以重构它们的属性,也可以调用它们的任何暴露的方法。

要动态地导入一个默认的导出,我们可以这样做。

// loader.js
const btn = document.getElementById("btn");


btn.addEventListener("click", () => {
  // loads entire module
  // runs default export
  import("./util.js").then((module) => {
    module.default();
  });
});

当整体导入一个模块时,我们可以使用它的所有输出。

// loader.js
const btn = document.getElementById("btn");


btn.addEventListener("click", () => {
  // loads entire module
  // uses everything
  import("./util.js").then((module) => {
    module.funcA();
    module.default();
  });
});

还有一种常见的动态导入方式,我们在文件的顶部提取逻辑。

const loadUtil = () => import("./util.js");


const btn = document.getElementById("btn");


btn.addEventListener("click", () => {
  //
});

在这里,loadUtil 将返回一个 Promise,准备进行链锁。

const loadUtil = () => import("./util.js");


const btn = document.getElementById("btn");


btn.addEventListener("click", () => {
  loadUtil().then(module => {
    module.funcA();
    module.default();
  });
});

动态导入看起来很好,但是它们有什么用呢?

通过动态导入,我们可以拆分我们的代码,只在合适的时刻加载重要的内容。在动态导入登陆JavaScript之前,这种模式是webpack这个模块捆绑器的专属。

像React和Vue这样的前端库,就大量使用了通过动态导入进行代码拆分的方式,在响应事件时加载分块代码,比如用户交互或者路由变化。

JSON文件的动态导入

假设你在代码库的某个地方有一个JSON文件person.json。

{
  "name": "Jules",
  "age": 43
}

现在,你想动态地导入这个文件,以响应一些用户的交互。

由于JSON文件导出的只是一个默认的导出,它不是一个函数,所以你只能像这样访问默认的导出。

const loadPerson = () => import("./person.json");


const btn = document.getElementById("btn");


btn.addEventListener("click", () => {
  loadPerson().then(module => {
    const { name, age } = module.default;
    console.log(name, age);
  });
});

这里,我们从默认的导出中重构name和age。

    const { name, age } = module.default;

使用async/await动态导入

import()语句返回的总是一个Promise,这意味着我们可以对它使用async/await。

const loadUtil = () => import("./util.js");


const btn = document.getElementById("btn");


btn.addEventListener("click", async () => {
  const utilsModule = await loadUtil();
  utilsModule.funcA();
  utilsModule.default();
});

动态导入名称

当用import()导入一个模块时,你可以随心所欲地给它命名,只要保持一致即可。

  import("./util.js").then((module) => {
    module.funcA();
    module.default();
  });

或者:

  import("./util.js").then((utilModule) => {
    utilModule.funcA();
    utilModule.default();
  });

文章来源于公众号:前端开发博客

以上就是W3Cschool编程狮关于全面认识ECMAScript模块的相关介绍了,希望对大家有所帮助。