Modules

JavaScript Modules (JavaScript模块)

With growing the application more extensive, there is a need to split it into multiple files: modules. As a rule, a module includes a class or a library of functions.

For some time, JavaScript existed without a language-level module syntax. That wasn’t a difficulty as the scripts were small and straightforward. (有一段时间, JavaScript没有语言级别的模块语法。这不是一个难题,因为剧本小而直截了当。)

But, over time, the scripts became more and more complex; hence a variety of ways were invented to organize the code into libraries and modules.

To make things more obvious, let’s take a glance at the first module systems:

  • AMD: one of the oldest module systems, initially enforced by the library require.js.

  • CommonJS: the module system that is made for the server of Node.js.

  • UMS: another module system, suggested as a universal system, compatible with Common.JS and AMD.

Today, the modules systems above are part of history. But, at times, you can find them in old scripts. (如今,上述模块系统已成为历史的一部分。但是,有时您可以在旧脚本中找到它们。)

The modern language-level system came out in 2015. Now, it is supported in Node.js and all major browsers. (现代语言水平系统于2015年问世。现在, Node.js和所有主要浏览器都支持它。)

Description of a Module

Description of a Module (模块说明)

Modules are files. One script is equal to one module. (模块是文件。一个脚本等于一个模块。)

Just as authors divide their books into chapters, programmers divide the programs into modules. Hence, modules can be considered as clusters of words. (就像作者将他们的书分成章节一样,程序员将程序分成模块。因此,模块可以被认为是单词的集群。)

Modules are capable of loading each other and using particular directives, such as export and import for exchanging functionality, call functions of one module from another one. It is explained below:

  • export labels variables and functions that need to be reached from outside the current module. (-导出需要从当前模块外部访问的标签变量和函数。)

  • import keyword enables the import of functionality from other modules. (- import关键字启用从其他模块导入功能。)

Let’s consider having a file welcome.js , which exports a function, like this:

//  welcome.js
export function welcome(site) {
 console.log(`Welcome to ${site}`);
}

Another file can import and use it. The import directive will load the module by path import close to the current file, assigning the exported function import to the matching variable. (另一个文件可以导入并使用它。import指令将通过靠近当前文件的路径import加载模块,将导出的函数import分配给匹配的变量。)

Here is an example:

// main.js
import {
 welcome
} from './welcome.js';
console.log(welcome); // function...
welcome('w3cdoc'); // Welcome to w3cdoc

Let’s try to run it in-browser. Since modules support particular features and keywords, it is necessary to tell the browser to treat the script as a module by applying the attribute <script type=“module”>.

It is demonstrated below:

Index.html

Index.html

<!doctype html>
<script type="module">
 import {welcome} from './welcome.js';  
 document.body.innerHTML = welcome('w3cdoc');
</script>

welcome.js

welcome.js

export function welcome(site) {
 return `Welcome to ${site}`;
}

The browser automatically fetches and evaluates the module, which is imported and only then runs the script. (浏览器自动获取和评估模块,该模块被导入,然后才运行脚本。)

Core Module Features

Core Module Features (核心模块功能)

Now, let’s monitor the differences between modules and regular scripts. We can distinguish core features that are valid for browser and server-side JavaScript. (现在,让我们监控模块和常规脚本之间的差异。 我们可以区分适用于浏览器和服务器端JavaScript的核心功能。)

Always “use strict”

Always “use strict” (始终“严格使用”)

By default, modules use strict. Thus, in case you assign to an undeclared variable, an error will occur. (默认情况下,模块使用严格。因此,如果分配给未声明的变量,将发生错误。)

For a better understanding, please, look at this example:

<script type="module">
 x = 10; // error
</script>

Module-level Scope

Module-level Scope (模块级范围)

Every module has a top-level scope. Otherwise speaking, you can’t find top-level variables and functions from a module in other scripts. (每个模块都有一个顶级范围。否则,您无法从其他脚本中的模块中找到顶级变量和函数。)

Let’s check out an example:

Index.html

Index.html

<!doctype html>
<script type="module" src="welcome.js"></script>

site.js

site.js

export let site = "w3cdoc";

welcome.js

welcome.js

import {
 site
} from './user.js';
document.body.innerHTML = site; // w3cdoc

In the browser, for each <script type=“module”> there is independent top-level scope. And, if you need to create a window-level global variable, you can explicitly assign it to window accessing as window.site .

Here is an example:

<script type="module">
 // The variable is only visible in this module script
(//变量仅在此模块脚本中可见)
 let site = "w3cdoc";
</script>
<script type="module">
 console.log(site); // Error: site is not defined
</script>

That’s, of course, an exception that requires a strong case. (当然,这是一个例外,需要一个强有力的案例。)

A Module Code Should be Evaluated Only the First Time When Imported

A Module Code Should be Evaluated Only the First Time When Imported (模块代码应仅在导入时首次评估)

In case the same module is imported into different places, its code is implemented only the first time. Afterward, the exports are given to all the importers. (如果相同的模块被导入到不同的地方,其代码仅在第一次实现。之后,出口将提供给所有进口商。)

It brings significant consequences. Let’s observe them with the help of examples. (这会带来严重后果。让我们借助示例来观察它们。)

First of all, if implementing a module code leads to side-effects ( for example, showing a message), importing it multiple times will activate it only the first time, like here:

// consoleLog.js
console.log("Module is evaluated!");
// Import the same module from different files
// One.js
import `./consoleLog.js`; // Module is evaluated!
// Two.js
import `./consoleLog.js`; // (shows nothing)

In practice, programmers use top-level module code mostly for initialization, the formation of internal data structures. If you want something to become reusable, you can export it. (在实践中,程序员主要使用顶级模块代码进行初始化,形成内部数据结构。如果您希望某些内容可以重复使用,可以将其导出。)

A module exports an object, in the example below:

// book.js
export let book = {
 bookTitle: "Javascript"
};

In case this module is exported from multiple files, the module will be evaluated only the first time, a book object will be created and passed to all the next importers. (如果从多个文件导出此模块,则仅在第一次评估模块,将创建一个图书对象并将其传递给所有下一个导入程序。)

For instance:

// One.js
import {
 book
} from './book.js';
book.bookTitle = "Javascript";

// Two.js
import {
 book
} from './book.js';
console.log(book.bookTitle); // Javascript
// Both One.js and Two.js imported the same object
// Changes made in One.js are visible in Two.js

To sum up, let’s assume that the module can be executed once. Exports are created and then shared between the importers. (综上所述,让我们假设该模块可以执行一次。导出被创建,然后在导入程序之间共享。)

In case something transforms the book object, other modules see that. (如果有什么东西转换了book对象,其他模块会看到这一点。)

Behavior like that allows configuring modules on the first import. It is possible to set up its properties once, and it will be ready in the next imports, as well.
For example:

// book.js
export let book = {};

export function welcome() {
 console(`Start learning ${book.bookTitle}`);
}

In the example above, the book.js module provides certain functionality but expects the credentials to come into the object of book from outside. In init.js, you can set book.bookTitle: anyone will see it, including the calls implemented inside book.js itself.

Here is an example:

// init.js
import {
 book
} from './book.js';
book.bookTitle = "Javascript";

The book.bookTitle can also be seen by another module, like here:

// nother.js
 import {
   book,
(v.预订 ,订 ,著者,著作)
   welcome
(欢迎)
 } from './book.js';

alert(book.bookTitle); // Javascript
welcome(); // Start learning Javascript

Import.meta

Import.meta (您确定要从此CSV文件导入meta数据吗?)

The import.meta object comprises information about the current module. Its content relies on the environment. In the browser, it includes the URL of the script, a current web page URL if inside HTML. (Import.meta对象包含有关当前模块的信息。 其内容依赖于环境。 在浏览器中,它包含脚本的URL ,如果在HTML中,则包含当前网页的URL。)

To make it clearer, let’s check out an example:

<script type="module">
 console.log(import.meta.url); // script url 
</script>

“this” is Undefined in a Module

“this” is Undefined in a Module (“this”在模块中未定义)

In a module, the top-level this is undefined. (在模块中,这是未定义的顶层。)

Let’s compare it to non-module scripts with this as a global object. (让我们将其与非模块脚本进行比较,并将其作为全局对象。)

It will look as follows:

<script>
 console.log(this); // window
</script>
<script type="module">
 console.log(this); // undefined
</script>

Module Scripts are Deferred

Module Scripts are Deferred (模块脚本被延迟)

Module scripts are deferred; it has the same effect as defer attribute for external and inline scripts.

Otherwise speaking:

  • if you download external module scripts <script type=“module” src="…">, it will not block HTML processing: they load along with other resources.

  • the module scripts wait till the HTML document is ready, then run. (-模块脚本等待HTML文档准备就绪,然后运行。)

  • the relative sequence of the scripts is maintained. It means that scripts that go first in the document, execute first. (-保持脚本的相对顺序。这意味着首先执行文档中的脚本。)

There is a side-effect, though: module scripts “see” the full-loaded HTML page, in particular, HTML elements below them.

To illustrate that, let’s consider the following example:

<script type="module">
 console.log(typeof button);//object: the button below can be “seen” by the script, as modules are deferred. 
//The script may run after the overall page is loaded
</script>

Compare to regular script below:

<script>
 console.log(typeof button); // Error: button is undefined
  // the script can't see elements below
(//脚本无法看到下面的元素)
 // regular scripts run immediately,
(//常规脚本立即运行,)
 // before the rest of the page is processed
</script>
<button id="button">Button</button>

Please, take into account that the second strip runs before the first. Hence, in the first place, you will see undefined, and then the object. (请记住,第二个试纸在第一个试纸之前运行。因此,首先,您将看到undefined ,然后是object。)

The reason is that modules are deferred: so you need to wait for the document to be processed. On the contrary, the regular script runs at once, so its output is noticed first.

While using modules, one should realize that the HTML page shows up as it loads. JavaScript modules run after that in a way, the user can see the page before the app is ready. But some functionality may not work yet. It is necessary to put “loading indicators”, ensuring that the visitor will not get confused. (在使用模块时,应该意识到HTML页面在加载时会显示出来。JavaScript模块以某种方式在此之后运行,用户可以在应用准备就绪之前看到页面。但有些功能可能还不能使用。有必要放置“加载指示器” ,以确保访客不会感到困惑。)

Async Works on Inline Scripts

Async Works on Inline Scripts (异步适用于内联脚本)

For non-module scripts, the async attribute operates only on external scripts. Async scripts run when ready regardless of the other scripts or the HTML document. (对于非模块脚本, async属性仅在外部脚本上运行。无论其他脚本或HTML文档如何,异步脚本都会在准备就绪时运行。)

As for the module scripts, it operates on inline scripts, as well. (至于模块脚本,它也在内联脚本上运行。)

Let’s consider an example where the inline script has async. So, it will not wait for anything. (让我们考虑一个示例,其中内联脚本具有异步。因此,它不会等待任何东西。)

It will implement the import (fetches ./counts.js) and run when ready, even if the HTML document is not over yet. (它将实现导入( fetches./counts.js )并在准备就绪时运行,即使HTML文档尚未结束。)

Here is how it looks like:

<!-- all dependencies are fetched (counts.js), and the script runs -->
<!-- doesn't expect a document or other <script> tags -->
<script async type="module">
 import {counter} from './countss.js';  
 counter.count();
</script>

External Scripts

External Scripts (外部脚本)

The external scripts, having type=“module” differ in two aspects. (Type = “module"的外部脚本在两个方面有所不同。)

<!-- the script script.js is fetched and executed only once →
<script type="module" src="script.js"></script>
<script type="module" src="script.js"></script>

You should do that to contribute to security. (您应该这样做,以促进安全。)

No “bare” Modules Allowed

No “bare” Modules Allowed (不允许“裸”模块)

By default, import gets a relative or an absolute URL. The modules having no path are known as “bare”. Modules like this are not allowed in import. (默认情况下, import获取相对或绝对URL。没有路径的模块被称为“裸”。导入时不允许使用此类模块。)

For a better understanding, let’s see an example of an invalid import:

import {
 welcome
} from 'welcome'; // Error, "bare" module
// the module must have a path, e.g. './welcome.js' or wherever the module is

But, Node.js or bundle tools allow bare modules, without a path, because they have ways for finding modules and hooks to adjust them. However, browsers don’t support bare modules so far. (但是, Node.js或捆绑工具允许没有路径的裸模块,因为它们可以找到模块和钩子来调整它们。但是,到目前为止,浏览器还不支持裸模块。)

Compatibility, “nomodule”

Compatibility, “nomodule” (兼容性, “nomodule”)

The type=“module” is not understood by old browsers. Hence, they ignore scripts of an unknown type. They can provide a fallback using the nomodule attribute, as follows:

<script type="module">
 console.log("Runs in browsers");
</script>
<script nomodule>
 console.log("Modern browsers know both type=module and nomodule, so skip this")
(console.log ("现代浏览器知道type = module和nomodule ,所以跳过这个"))
 console.log("Old browsers ignore script with unknown type=module, but execute this.");
</script>

Build Tools

Build Tools (用工具建造)

In practice, browser modules are seldom used in their “plain” form. As a rule, developers mix them with a unique tool called Webpack and deploy to the production server. (实际上,浏览器模块很少以“普通”形式使用。通常,开发人员会将它们与称为Webpack的独特工具混合并部署到生产服务器。)

One of the most significant assets of using bundlers is that they give more control over how modules are resolved, allowing bare modules and so on. (使用捆绑包最重要的资产之一是,它们可以更好地控制模块的解析方式,允许裸模块等等。)

The build tools operate as follows:

<!-- Assuming we got script.js from a tool like Webpack -->
<script src="script.js"></script>


请遵守《互联网环境法规》文明发言,欢迎讨论问题
扫码反馈

扫一扫,反馈当前页面

咨询反馈
扫码关注
返回顶部