乐趣区

关于javascript:2020-Javascript-模块

在这篇文章中,咱们将介绍规范的 JavaScript 模块,目前是如何在前端应用程序中应用的,以及将来咱们可能会如何应用它们。
JavaScript 模块有时被称为 ESM,它代表 ECMAScript 模块。

什么是 JavaScript 模块?

JavaScript模块是结构 JavaScript 代码的一种办法。模块中的代码与其余模块中的代码是隔离的,并且不在全局范畴内。

<script>
  function hello() {console.log("hello Bob");
  }
</script>
<script>
  function hello() {console.log("hello Fred");
  }
</script>
<script>
  hello(); // outputs hello Fred
</script>

下面的代码定义了两个函数,没有应用模块,在全局作用域会产生抵触。

JavaScript 模块解决的另一个问题是不用放心 HTML 页面上脚本元素的程序:

<script>
  hello(); // ???? - Uncaught ReferenceError: hello is not defined
</script>
<script>
  function hello() {console.log("hello");
  }
</script>

在下面的示例中,定义 hello 函数的脚本元素须要放在调用 hello 函数的脚本元素之前。如果有很多这样的 Javascript 文件,就很难治理了。

当初 JavaScript 模块通常是如何应用的?

JavaScript 模块语法是在 ES6 中引入的,通常在咱们明天构建的应用程序中应用,如下所示:

import React from 'react';
...
export const HomePage = () => ...

下面的示例导入 React 模块并导出 HomePage 组件。

不过,这段代码并没有应用 JavaScript 模块。取而代之的是,Webpack 将其转换为非原生模块,而采纳了 IIFE(立刻调用函数表达式)来做。
值得注意的是,Webpack 的确有一个实验性的 outputModule 个性,容许它以原生模块格局公布。心愿 Webpack 5 中蕴含这个性能!

应用原生的 JavaScript 模块

要申明一个援用 JavaScript 模块代码的脚本元素,须要将类型属性设置为module:

<script type="module">
  import {hello} from "/src/a.js";
  hello();
</script>

这是 src 文件夹中 a.js 中的 JavaScript

// /src/a.js
import {hellob} from "/src/b.js";
import {helloc} from "/src/c.js";

export function hello() {hellob();
  helloc();}

因而,在 a.js 中的 hello 函数,调用了在 b.js 中调用 hellob,在 c.js 中调用 helloc

这是来自 b.jsc.jsJavaScript

// /src/b.js
export function hellob() {console.log("hello b");
}
// /src/c.js
export function helloc() {console.log("hello c");
}

请留神,咱们须要提供要导入的文件的残缺相对路径,并且还须要蕴含文件扩展名。
咱们可能更习惯于一个简略的导入说明符,如下所示:

import {hello} from "a";

稍后咱们将再次介绍原生的导入说明符。

还请留神,咱们不用在 HTML 文件中申明所有模块。
浏览器在运行时会去解析它们。

须要留神的是,不能从一般脚本元素应用 JavaScript 模块。
例如,如果咱们尝试不应用 type 属性,脚本元素将不会被执行:

<script>
  // ???? - 不能在模块内部应用 import 语句
  import {hello} from "/src/a.js";
  hello();
</script>

JavaScript 模块编写的代码在默认状况下以 严格模式 执行。
所以没有必要在代码顶部应用 use strict:

<script type="module">
  let name = "Fred";
  let name = "Bob"; // ???? - Identifier 'name' has already been declared
</script>

JavaScript 模块谬误

让咱们以后面的相似示例为例,其中有 JavaScript 模块 a,b,c。模块 a 依赖于模块 bc。模块 bc 没有依赖关系。

假如 c.js 中蕴含运行时谬误:

export function helloc() {consol.log("hello c"); // ???? - Uncaught ReferenceError: consol is not defined
}

HTML 文件调用代码的办法如下:

<script type="module">
  import {hello} from "/src/a.js";
  hello();
</script>

a.js文件中:

import {hellob} from "/src/b.js";
import {helloc} from "/src/c.js";

export function hello() {hellob();
  helloc(); // ????
  hellob(); // never executed 从未执行}

正如咱们所意料的那样,第二次调用 hellob 时永远不会被调用。

如果 c.js 中的问题是编译谬误:

// 注:错写了 function 这个单词
export functio helloc() {console.log("hello c");
}

模块中没有代码被执行:

<script type="module">
  // ???? - Unexpected token 'export'
  // no code is executed
  import {hello} from "/src/a.js";
  hello();
</script>

其余模块中的代码能够失常执行。[_注:再有一个 script 设置 type 为 module,能够失常执行,并不会受报错模块的影响,因为每个模块是独立的,没依赖关系互不受影响]_
_

浏览器反对

所有的古代浏览器都反对原生模块,但可怜的是,IE 不反对。
然而,有一种办法能够让咱们在反对原生模块的浏览器上应用它们,并为不反对它们的浏览器提供一种进路。
应用 script 元素上的 nomodule 属性来实现这一点:

[注:设置了 nomodule,在反对原生模块的浏览器中不执行,可用于在不反对模块化 JavaScript 的旧浏览器中提供回退脚本]


<!-- 反对原生模块浏览器执行 -->
<script type="module" src="app.js"></script>
<!-- 不反对原生模块浏览器执行 -->
<script nomodule src="classic-app-bundle.js"></script>

Rollup,能够很好地输入 ES 模块文件和非 ES 模块文件:

export default [{
  ...
  output: {
    file: 'bundle-esm.js',
    format: 'es'
  }
},{
  ...
  output: {
    file: 'bundle.js',
    format: 'iife'
  }
}];

瀑布流式

让咱们看看一个示例,其中有援用来自 CDN 的模块:

<script type="module">
  import intersection from "https://cdn.jsdelivr.net/npm/lodash-es@4.17.15/intersection.min.js";
  console.log(intersection([2, 1], [2, 3]));
</script>

模块依赖于其余模块,而这些模块又依赖于其余模块。因而,在执行脚本元素之前,会下载并解析所有依赖项。

预加载模块

JavaScript 模块能够预加载应用 modulepreload 资源提醒:

<link
  rel="modulepreload"
  href="https://cdn.jsdelivr.net/npm/lodash-es@4.17.15/intersection.js"
/>

即在其余模块下载之前先下载并解析此模块:

目前只有 ChromeEdge 反对预加载模块。Firefox Safari 将对模块失常下载。

动静导入

动静导入 是在运行时能够依据不同条件在其中导入代码:

<script type="module">
  if (new Date().getSeconds() < 30) {import("/src/a.js").then(({helloa}) =>
      helloa());
  } else {import("/src/b.js").then(({hellob}) =>
      hellob());
  }
</script>

这对于某些使用率较低的大型模块很有用。这也能够缩小浏览器中应用程序的内存占用。

应用导入映射说明符

回到咱们如何在 import 语句中援用模块:

import {hello} from "/src/a.js";
import intersection from "https://cdn.jsdelivr.net/npm/lodash-es@4.17.15/intersection.min.js

如果咱们认真想想,除非指定模块的残缺门路,否则浏览器怎么晓得在哪里找到它呢?
所以,语法是有意义的,即便咱们不习惯它。

有一种办法能够将导入说明符与一个被提议的称为导入映射 (import-maps) 的个性一起应用。
这是一个在非凡的 importmap 脚本元素中定义的映射,须要在援用模块的脚本元素之前定义:

<script type="importmap">
  {
    "imports": {
      "b": "/src/b.js",
      "lowdash-intersection": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.15/intersection.min.js"
    }
  }
</script>

为每个依赖模块提供一个纯导入说明符名称。而后能够在 import 语句中应用定义好的说明符:

<script type="module">
  import {hellob} from "b";
  hellob();
  import intersection from "lowdash-intersection";
  console.log(intersection([2, 1], [2, 3]));
</script>

目前,导入映射在浏览器中不可用。然而,此性能可通过以下实验性标记在 Chrome 中应用:chrome:// flags /#enable-experimental-web-platform-features
**

依赖项必须公布的是 ES 模块

重要的一点是,库必须公布为原生模块格局,以便开发者将库用作原生模块应用。可怜的是,目前这种状况并不常见。例如,React 尚未公布为原生模块。

原生模块绝对于非原生模块的益处

与 IIFE 模块之类的模块相比,原生模块的一个益处是须要下载到浏览器、解析而后执行,相对来说代码更少。
原生模块也能够并行地、异步地下载和解析。因而,原生模块对于大型依赖树可能执行得更快。
此外,预加载模块可能意味着用户能够更快地与页面交互,因为这些代码是从主线程中解析进去的。

除了性能上的进步之外,新的浏览器个性还可能构建在模块之上,因而应用原生模块是一种验证将来的代码。

结束语

以后最风行的浏览器都能够应用本机模块,并且能够为 IE 提供备份。Rollup 曾经能够以这种格局公布了,而且 Webpack 反对仿佛正在进行中。当初,咱们所须要的是更多的库开始以原生模块公布。

github 博客地址:https://github.com/WYseven/bl…。
如果对你有帮忙,请关注【前端技能解锁】:

退出移动版