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.jsimport { 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.js 和 c.js 的 JavaScript:
// /src/b.jsexport function hellob() {
console.log("hello b");
}
// /src/c.jsexport 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 依赖于模块 b 和 c。模块 b 和c没有依赖关系。
假设 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"></script>
<!--不支持原生模块浏览器执行-->
<script nomodule></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 资源提示:
<linkrel="modulepreload"
href="https://cdn.jsdelivr.net/npm/lodash-es@4.17.15/intersection.js"
/>
即在其他模块下载之前先下载并解析此模块:
目前只有 Chrome 和 Edge 支持预加载模块。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...。
如果对你有帮助,请关注【前端技能解锁】:
以上是 Javascript 模块 的全部内容, 来源链接: utcz.com/a/41692.html