Home >Web Front-end >JS Tutorial >An article explaining modularity in es6 in detail

An article explaining modularity in es6 in detail

青灯夜游
青灯夜游forward
2022-11-02 20:17:171742browse

An article explaining modularity in es6 in detail

CommonJs has many excellent features. Let’s briefly review them below:

  • The module code is only loaded when After running;

  • The module can only be loaded once;

  • The module can request to load other modules;

  • Support circular dependencies;

  • Modules can define public interfaces, and other modules can observe and interact based on this public interface;

The unique thing about CommonJs CommonJs


Es Module is that it can be loaded natively through the browser or with third-party loaders and build tools load.

Browsers that support Es module modules can load the entire dependency graph from the top-level module, and it is done asynchronously. The browser will parse the entry module, determine the dependencies, and send a request for the dependent module. After these files are returned over the network, the browser will resolve their dependencies, and if these secondary dependencies have not been loaded, more requests will be sent.

This asynchronous recursive loading process will continue until the entire application's dependency graph has been resolved. After the dependency graph is parsed, the reference program can officially load the module.

Es Module not only borrows many excellent features of CommonJs and AMD, but also adds some new behaviors:

  • Es Module Execute in strict mode by default;

  • ##Es Module Does not share global namespace;

  • Es Module The value of the top-level this is undefined (the regular script is window);

  • var declarations in modules are not added to the window object;

  • Es Module is loaded and executed asynchronously;

export and import


    ## module functions It mainly consists of two commands:
  • exports

    and import. The

  • export
  • command is used to specify the external interface of the module, and the import command is used to import the functions provided by other modules.

Basic use of export

Basic form of export:
  • export const nickname = "moment";
    export const address = "广州";
    export const age = 18;
Of course, you can also write it in the following form:
  • const nickname = "moment";
    const address = "广州";
    const age = 18;
    
    export { nickname, address, age };
Export an object and function to the outside world
  • export function foo(x, y) {
      return x + y;
    }
    
    export const obj = {
      nickname: "moment",
      address: "广州",
      age: 18,
    };
    
    // 也可以写成这样的方式
    function foo(x, y) {
      return x + y;
    }
    
    const obj = {
      nickname: "moment",
      address: "广州",
      age: 18,
    };
    
    export { foo, obj };
Normally,
    export
  • The output variable is its original name, but it can be renamed using the as keyword.
    const address = "广州";
    const age = 18;
    
    export { nickname as name, address as where, age as old };
Default export, it is worth noting that a module can only have one default export:
  • export default "foo";
    
    export default { name: 'moment' }
    
    export default function foo(x,y) {
      return x+y
    }
    
    export { bar, foo as default };

Incorrect use of export

The export statement must be at the top level of the module and cannot be nested in a block:
  • if(true){
    export {...};
    }
    export
  • Must provide an external interface:
    // 1只是一个值,不是一个接口
    export 1
    
    // moment只是一个值为1的变量
    const moment = 1
    export moment
    
    // function和class的输出,也必须遵守这样的写法
    function foo(x, y) {
        return x+y
    }
    export foo

Basic usage of import

Usage
    export
  • After the command defines the external interface of the module, other js files can load the entire module through the import command.
    import {foo,age,nickname} form '模块标识符'
The module identifier can be the relative path of the current module. It can also be an absolute path or a pure string, but it cannot be the result of dynamic calculation, such as a string.
  • import
  • The command accepts a curly bracket, which specifies the variable name to be imported from other modules, and the variable name must be the same as the name of the external interface of the imported module. The imported variable cannot be reassigned because it is a read-only interface. If it is an object, the properties of the object can be reassigned. Exported modules can modify values, and imported variables will also change accordingly.

An article explaining modularity in es6 in detail

As can be seen from the above picture, the properties of the object have been reassigned, while the variables have reported
    Assignment to constant variable
  • has the wrong type. If the module exports both named exports and default exports, they can be obtained at the same time in the
  • import
  • statement. You can list specific identifiers in order to obtain them, or you can use * to obtain them:
    // foo.js
    export default function foo(x, y) {
      return x + y;
    }
    
    export const bar = 777;
    
    export const baz = "moment";
    
    // main.js
    import { default as foo, bar, baz } from "./foo.js";
    
    import foo, { bar, baz } from "./foo.js";
    
    import foo, * as FOO from "./foo.js";

dynamic import

Standard usage
    import
  • The imported module is static, which will cause all imported modules to be compiled when loading (compilation on demand cannot be achieved, which reduces the loading speed of the homepage ). In some scenarios, you may want to import modules based on conditions or on demand. In this case, you can use dynamic import instead of static import. Keywords
  • import
  • You can dynamically import modules just like calling functions. Called this way, a promise will be returned.
    import("./foo.js").then((module) => {
      const { default: foo, bar, baz } = module;
      console.log(foo); // [Function: foo]
      console.log(bar); // 777
      console.log(baz); // moment
    });

Use top-level await

  • 在经典脚本中使用 await 必须在带有 async 的异步函数中使用,否则会报错:
const p = new Promise((resolve, reject) => {
  resolve(111);
});

// SyntaxError: await is only valid in async functions and the top level bodies of modules
const result = await p;

console.log(result);
  • 而在模块中,你可以直接使用 Top-level await:
const p = new Promise((resolve, reject) => {
  resolve(777);
});

const result = await p;

console.log(result); // 777正常输出

import 的错误使用

  • 由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
// 错误
import { 'b' + 'ar' } from './foo.js';

// 错误
let module = './foo.js';
import { bar } from module;

// 错误
if (x === 1) {
  import { bar } from './foo.js';
} else {
  import { foo } from './foo.js';
}

在浏览器中使用 Es Module


  • 在浏览器上,你可以通过将 type 属性设置为 module 用来告知浏览器将 script 标签视为模块。

<script type="module" src="./main.mjs"></script>
<script type="module"></script>
  • 模块默认情况下是延迟的,因此你还可以使用 defer 的方式延迟你的 nomodule 脚本:
  <script type="module">
      console.log("模块情况下的");
    </script>
    <script src="./main.js" type="module" defer></script>
    <script>
      console.log("正常 script标签");
    </script>

An article explaining modularity in es6 in detail

  • 在浏览器中,引入相同的 nomodule 脚本会被执行多次,而模块只会被执行一次:
    <script src="./foo.js"></script>
    <script src="./foo.js"></script>

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

An article explaining modularity in es6 in detail

模块的默认延迟


  • 默认情况下,nomodule 脚本会阻塞 HTML 解析。你可以通过添加 defer 属性来解决此问题,该属性是等到 HTML 解析完成之后才执行。

An article explaining modularity in es6 in detail

  • deferasync 是一个可选属性,他们只可以选择其中一个,在 nomodule 脚本下,defer 等到 HTML 解析完才会解析当前脚本,而 async 会和 HTML 并行解析,不会阻塞 HTML 的解析,模块脚本可以指定 async 属性,但对于 defer 无效,因为模块默认就是延迟的。
  • 对于模块脚本,如果存在 async 属性,模块脚本及其所有依赖项将于解析并行获取,并且模块脚本将在它可用时进行立即执行。

Es Module 和 Commonjs 的区别


讨论 Es Module 模块之前,必须先了解 Es ModuleCommonjs 完全不同,它们有三个完全不同:

  • CommonJS 模块输出的是一个值的拷贝,Es Module 输出的是值的引用;

  • CommonJS 模块是运行时加载,Es Module 是编译时输出接口。

  • CommonJS 模块的 require() 是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 Es Module 不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

Commonjs 输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。具体可以看上一篇写的文章。

Es Module 的运行机制与 CommonJS 不一样。JS引擎 对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,import就是一个连接管道,原始值变了,import 加载的值也会跟着变。因此,Es Module 是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

Es Module 工作原理的相关概念


  • 在学习工作原理之前,我们不妨来认识一下相关的概念。

Module Record

模块记录(Module Record) 封装了关于单个模块(当前模块)的导入和导出的结构信息。此信息用于链接连接模块集的导入和导出。一个模块记录包括四个字段,它们只在执行模块时使用。其中这四个字段分别是:

  • Realm: 创建当前模块的作用域;

  • Environment:模块的顶层绑定的环境记录,该字段在模块被链接时设置;

  • Namespace: A module namespace object is a module namespace foreign object that provides runtime property-based access to module export bindings. The module namespace object has no constructor;

  • HostDefined: Field reserved for use by host environments, additional information is required to be associated with the module .

Module Environment Record

  • The module environment record is a declarative environment record used to represent External scope of ECMAScript modules. In addition to ordinary mutable and immutable bindings, module environment records also provide immutable import bindings that provide binding to a target that exists in another environment record indirect access.

Immutable binding means that the current module introduces other modules, and the introduced variables cannot be modified. This is the unique immutable binding of the module.

Es Module parsing process


Before we start, let’s first have a general idea of ​​what the entire process is like. Understanding:

  • Phase 1: Construction (Construction), find the js file according to the address, download it through the network, and parse it The module file is Module Record;

  • Phase 2: Instantiation (Instantiation), instantiate the module and allocate memory space. Parse the import and export statements of the module and point the module to the corresponding memory address;

  • Phase 3: Run (Evaluation), run the code, calculate the value, and The value is filled into the memory address;

Construction construction phase

  • loader Responsible for addressing and downloading modules. First we modify an entry file, which in HTML is usually a <script type="module"></script> tag to represent a module file.

An article explaining modularity in es6 in detail

  • The module continues to be declared through the import statement. There is a module declaration identifier in the import declaration statement. Character (ModuleSpecifier), which tells loader how to find the address of the next module.

An article explaining modularity in es6 in detail

  • Each module identification number corresponds to a module record (Module Record), and each module record Contains JavaScript code, execution context, ImportEntries, LocalExportEntries, IndirectExportEntries, StarExportEntries . The ImportEntries value is a ImportEntry Records type, and LocalExportEntries, IndirectExportEntries, StarExportEntries is a ExportEntry Records type.

ImportEntry Records

  • A ImportEntry Records contains three fields ModuleRequest, ImportName, LocalName;

ModuleRequest: a module identifier (ModuleSpecifier);

ImportName: by ModuleRequest The name of the required binding for the module export of the module identifier. The value namespace-object indicates that the import request is for the namespace object of the target module;

LocalName: variable used to access the imported value from the current module from the imported module;

  • Please refer to the figure below for details:An article explaining modularity in es6 in detail
  • The following table records examples of ImportEntry Records fields imported using import:
##import React from "react";"react""default ""React"import * as Moment from "react";"react"namespace-obj"Moment"import {useEffect} from "react";"react""useEffect""useEffect"import {useEffect as effect } from "react";"react""useEffect""effect"

ExportEntry Records

  • A ExportEntry Records contains four fields ExportName, ModuleRequest,ImportName, LocalName, and ImportEntry Records are different in that there is an additional ExportName.

ExportName: The name this module uses to bind when exporting.

  • The following table records examples of ExportEntry Records fields exported using export:

Import Statement (Import Statement Form) Module Identifier (ModuleRequest) Import Name (ImportName) LocalName(LocalName)
##export * as ns from "mod";" ns"mod"allnull
  • Back to topic

  • Only after parsing the current Module Record can we know which submodules the current module depends on , then you need to resolve the submodule, obtain the submodule, then parse the submodule, and continuously cycle this process resolving -> fetching -> parsing. The result is as shown in the figure below:

  • An article explaining modularity in es6 in detail

    • This process is also called static analysis. It will not run JavaScript code and will only identify export and import keyword, so import cannot be used in non-global scope, except for dynamic import.
    • What if multiple files depend on one file at the same time? Will this cause an infinite loop? The answer is no.
    • loader Use Module Map to track and cache the global MOdule Record to ensure that the module is only fetch Once, there will be an independent Module Map in each global scope.

    MOdule Map is a key/value mapping object composed of a URL record and a string. The URL record is the request URL to get the module, a string indicating the type of module (e.g. "javascript"). The value of the module map is either the module script, null (used to indicate a failed fetch), or the placeholder value "fetching".

    An article explaining modularity in es6 in detail

    linking linking phase

    • In allModule Record After being parsed, the JS engine needs to link all modules. The JS engine takes the Module Record of the entry file as the starting point, recursively links the modules in depth-first order, and creates a Module Environment Record for each Module Record. Used to manage variables in Module Record.

    An article explaining modularity in es6 in detail

    • ##Module Environment Record has a Binding, which is used to store Module The variables exported by Record, as shown in the figure above, export a variable of count at the module main.js, in Module Environment Record The Binding will have a count. At this time, it is equivalent to the compilation phase of V8, creating a module instance object, adding the corresponding attributes and Method, the value at this time is undefined or null, allocate memory space for it.
    • The
    • import keyword is used in the submodule count.js to import main.js, and count. The import of js and the export variable of main.js point to the same memory location, thus linking the relationship between the parent and child modules. Woke up. As shown in the figure below:

    An article explaining modularity in es6 in detail

    ##It should be noted that we call
      export
    • exported as the parent module, import The submodule is introduced. The parent module can modify variables and has read and write permissions, while the submodule only has read permissions.

    Evaluation evaluation phase

    After the modules are linked to each other, execute the top-level scope in the corresponding module file Code that determines the values ​​of variables defined in the link phase and places them in memory.
    How Es module solves circular references


      There are 5 in
    • Es Module

      The states are unlinked, linking, linked, evaluating and evaluated respectively, recorded using the loop module ( Cyclic Module Records) is represented by the Status field. It is through this field that it is judged whether the module has been executed. Each module is only executed once. This is also why Module Map is used for global caching Module Record. If the status of a module is evaluated, then the next execution will automatically Skip, thus wrapping a module will only be executed once. Es Module uses the depth first method to traverse the module graph. Each module is only executed once, which avoids an infinite loop.

    深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深地搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

    An article explaining modularity in es6 in detail

    • 看下面的例子,所有的模块只会运行一次:
    // main.js
    import { bar } from "./bar.js";
    export const main = "main";
    console.log("main");
    
    // foo.js
    import { main } from "./main.js";
    export const foo = "foo";
    console.log("foo");
    
    // bar.js
    import { foo } from "./foo.js";
    export const bar = "bar";
    console.log("bar");
    • 通过 node 运行 main.js ,得出以下结果:

    An article explaining modularity in es6 in detail

    • 好了,这篇文章到这也就结束了。

    原文地址:https://juejin.cn/post/7166046272300777508

    【推荐学习:javascript高级教程

    Export declaration Export name Module identifier Import name Local name
    export var v; "v" null null "v"
    export default function f() {} "default" null null "f"
    export default function () {} "default" null null "default"
    export default 42; "default" null null "default"
    export {x}; "x" null null "x"
    export {v as x}; "x" null null "v"
    export {x} from "mod"; "x" "mod" "x" null
    export {v as x} from "mod"; "x" "mod" "v" null
    export * from "mod"; null "mod" all-but-default null

    The above is the detailed content of An article explaining modularity in es6 in detail. For more information, please follow other related articles on the PHP Chinese website!

    Statement:
    This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete