Maison  >  Article  >  interface Web  >  Un article expliquant la modularité dans es6 en détail

Un article expliquant la modularité dans es6 en détail

青灯夜游
青灯夜游avant
2022-11-02 20:17:171623parcourir

Un article expliquant la modularité dans es6 en détail

CommonJs possède de nombreuses excellentes fonctionnalités. Passons-les brièvement en revue ci-dessous : CommonJs 有很多优秀的特性,下面我们再简单的回顾一下:

  • 模块代码只在加载后运行;

  • 模块只能加载一次;

  • 模块可以请求加载其他模块;

  • 支持循环依赖;

  • 模块可以定义公共接口,其他模块可以基于这个公共接口观察和交互;

天下苦 CommonJs 久矣


Es Module 的独特之处在于,既可以通过浏览器原生加载,也可以与第三方加载器和构建工具一起加载。

支持 Es module 模块的浏览器可以从顶级模块加载整个依赖图,且是异步完成。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器就会解析它们的依赖,,如果这些二级依赖还没有加载,则会发送更多请求。

这个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析完成依赖图,引用程序就可以正式加载模块了。

Es Module 不仅借用了 CommonJsAMD 的很多优秀特性,还增加了一些新行为:

  • Es Module 默认在严格模式下执行;

  • Es Module 不共享全局命名空;

  • Es Module 顶级的 this 的值是 undefined(常规脚本是window);

  • 模块中的 var 声明不会添加到 window 对象;

  • Es Module 是异步加载和执行的;

export 和 import


  • 模块功能主要由两个命令构成: exportsimport

  • export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

export的基本使用

  • 导出的基本形式:
export const nickname = "moment";
export const address = "广州";
export const age = 18;
  • 当然了,你也可以写成以下的形式:
const nickname = "moment";
const address = "广州";
const age = 18;

export { nickname, address, age };
  • 对外导出一个对象和函数
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 };
  • 通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
const address = "广州";
const age = 18;

export { nickname as name, address as where, age as old };
  • 默认导出,值得注意的是,一个模块只能有一个默认导出:
export default "foo";

export default { name: 'moment' }

export default function foo(x,y) {
  return x+y
}

export { bar, foo as default };

export 的错误使用

  • 导出语句必须在模块顶级,不能嵌套在某个块中:
if(true){
export {...};
}
  • export 必须提供对外的接口:
// 1只是一个值,不是一个接口
export 1

// moment只是一个值为1的变量
const moment = 1
export moment

// function和class的输出,也必须遵守这样的写法
function foo(x, y) {
    return x+y
}
export foo

import的基本使用

  • 使用 export 命令定义了模块的对外接口以后,其他js文件就可以通过 import 命令加载整个模块
import {foo,age,nickname} form '模块标识符'
  • 模块标识符可以是当前模块的相对路径,也可以是绝对路径,也可以是纯字符串,但不能是动态计算的结果,例如凭借的字符串。
  • import 命令后面接受一个花括弧,里面指定要从其他模块导入的变量名,而且变量名必须与被导入模块的对外接口的名称相同。
  • 对于导入的变量不能对其重新赋值,因为它是一个只读接口,如果是一个对象,可以对这个对象的属性重新赋值。导出的模块可以修改值,导入的变量也会跟着改变。

Un article expliquant la modularité dans es6 en détail

  • 从上图可以看得出来,对象的属性被重新赋值了,而变量的则报了 Assignment to constant variable 的类型错误。
  • 如果模块同时导出了命名导出和默认导出,则可以在 import 语句中同时取得它们。可以依次列出特定的标识符来取得,也可以使用 * 来取得:
// 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";

动态 import

  • 标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。
  • 关键字 import 可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise
    • Le code du module est uniquement dans Run. après le chargement ;

  • Les modules ne peuvent être chargés qu'une seule fois ;
  • Les modules peuvent demander le chargement d'autres modules ;🎜
  • 🎜Prend en charge les dépendances circulaires ;🎜🎜
  • 🎜Les modules peuvent définir des interfaces , d'autres modules peuvent observer et interagir en fonction de cette interface publique ; 🎜🎜🎜

    🎜tianxiaku CommonJs 久矣🎜


    🎜Es Module est unique en ce sens que les deux peuvent être chargé de manière native via le navigateur ou avec des chargeurs et des outils de construction tiers. 🎜🎜Les navigateurs qui prennent en charge le module Es module peuvent charger l'intégralité du graphique de dépendances à partir du module de niveau supérieur de manière asynchrone. Le navigateur analysera le module d'entrée, déterminera les dépendances et enverra une demande pour le module dépendant. Une fois ces fichiers renvoyés sur le réseau, le navigateur résoudra leurs dépendances, et si ces dépendances secondaires n'ont pas été chargées, d'autres requêtes seront envoyées. 🎜🎜Ce processus de chargement récursif asynchrone se poursuivra jusqu'à ce que l'ensemble du graphe de dépendances de l'application soit résolu. Une fois le graphe de dépendance analysé, le programme de référence peut officiellement charger le module. 🎜🎜Es Module emprunte non seulement de nombreuses excellentes fonctionnalités de CommonJs et AMD, mais ajoute également de nouveaux comportements : 🎜
    • 🎜Es Module est exécuté en mode strict par défaut ; 🎜🎜
    • 🎜Es Module ne partage pas le global espace de noms ; 🎜🎜
    • 🎜Es Module La valeur de this de niveau supérieur est undefined (le script normal est window code>); 🎜🎜<li>🎜La déclaration <code>var dans le module ne sera pas ajoutée à l'objet window 🎜🎜
    • 🎜Es Module ; est chargé et exécuté de manière asynchrone ;🎜🎜🎜

      🎜export et import🎜


      • 🎜La fonction du module se compose principalement de deux commandes : exports et <code>importer. 🎜🎜
      • La commande export est utilisée pour spécifier l'interface externe du module, et la commande import est utilisée pour importer les fonctions fournies par d'autres modules. 🎜🎜🎜🎜🎜Utilisation de base de l'export🎜🎜🎜
        • Forme de base de l'export : 🎜🎜
          import("./foo.js").then((module) => {
            const { default: foo, bar, baz } = module;
            console.log(foo); // [Function: foo]
            console.log(bar); // 777
            console.log(baz); // moment
          });
          • Bien sûr, vous pouvez également l'écrire sous la forme suivante : 🎜🎜
            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);
              Exporter un objet et une fonction vers l'extérieur 🎜🎜
              const p = new Promise((resolve, reject) => {
                resolve(777);
              });
              
              const result = await p;
              
              console.log(result); // 777正常输出
              • Normalement, la variable sortie par export est son nom d'origine, mais vous pouvez utiliser le as mot-clé Renommer. 🎜🎜<pre class="brush:js;toolbar:false;">// 错误 import { &amp;#39;b&amp;#39; + &amp;#39;ar&amp;#39; } from &amp;#39;./foo.js&amp;#39;; // 错误 let module = &amp;#39;./foo.js&amp;#39;; import { bar } from module; // 错误 if (x === 1) { import { bar } from &amp;#39;./foo.js&amp;#39;; } else { import { foo } from &amp;#39;./foo.js&amp;#39;; }</pre><ul><li>Export par défaut, il est à noter qu'un module ne peut avoir qu'un seul export par défaut : 🎜🎜<pre class="brush:js;toolbar:false;">&lt;script type=&quot;module&quot; src=&quot;./main.mjs&quot;&gt;&lt;/script&gt; &lt;script type=&quot;module&quot;&gt;&lt;/script&gt;</pre>🎜🎜🎜export utilisation incorrecte🎜🎜🎜<ul><li>La déclaration d'export doit être en le module Niveau supérieur, ne peut pas être imbriqué dans un bloc : 🎜🎜<pre class="brush:js;toolbar:false;"> &lt;script type=&quot;module&quot;&gt; console.log(&quot;模块情况下的&quot;); &lt;/script&gt; &lt;script src=&quot;./main.js&quot; type=&quot;module&quot; defer&gt;&lt;/script&gt; &lt;script&gt; console.log(&quot;正常 script标签&quot;); &lt;/script&gt;</pre><ul><li> <code>export Doit fournir une interface externe : 🎜🎜
                    <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>
                🎜🎜🎜Utilisation basique de l'import🎜🎜🎜
                • Après avoir utilisé la commande export pour définir l'interface externe du module, d'autres fichiers js peuvent charger l'intégralité du module via la commande import🎜🎜
                  // 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");
                  • Identification du module Le caractère peut être un chemin relatif vers le module actuel, un chemin absolu ou une chaîne pure, mais il ne peut pas être le résultat d'un calcul dynamique, tel qu'une chaîne de dépendances. 🎜
                  • import La commande accepte une accolade, qui spécifie le nom de la variable à importer depuis d'autres modules, et le nom de la variable doit être le même que le nom de l'interface externe du module importé . 🎜
                  • La variable importée ne peut pas être réaffectée car il s'agit d'une interface en lecture seule. S'il s'agit d'un objet, les propriétés de l'objet peuvent être réaffectées. Les modules exportés peuvent modifier les valeurs et les variables importées changeront également en conséquence. 🎜🎜🎜Un article expliquant la modularité dans es6 en détail🎜
                    • Comme le montre l'image ci-dessus, les propriétés de l'objet ont été réaffectées, tandis que la variable a signalé une erreur de type Affectation à une variable constante. 🎜
                    • Si le module exporte à la fois les exportations nommées et les exportations par défaut, elles peuvent être obtenues en même temps dans l'instruction import. Vous pouvez lister des identifiants spécifiques afin de les obtenir, ou vous pouvez utiliser * pour les obtenir : 🎜🎜rrreee🎜🎜🎜importation dynamique🎜🎜🎜
                      • Utilisation standard de import Les modules importés sont statiques, ce qui entraînera la compilation de tous les modules importés lors de leur chargement (la compilation à la demande ne peut pas être réalisée, ce qui réduit la vitesse de chargement de la page d'accueil). Dans certains scénarios, vous souhaiterez peut-être importer des modules en fonction de conditions ou à la demande. Dans ce cas, vous pouvez utiliser l'importation dynamique au lieu de l'importation statique. 🎜<li>Le mot-clé <code>import peut importer dynamiquement des modules comme appeler une fonction. Appelée de cette façon, une promesse sera renvoyée. 🎜🎜rrreee🎜🎜🎜Utilisez l'attente de niveau supérieur🎜🎜🎜
                        • 在经典脚本中使用 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 { &#39;b&#39; + &#39;ar&#39; } from &#39;./foo.js&#39;;
                        
                        // 错误
                        let module = &#39;./foo.js&#39;;
                        import { bar } from module;
                        
                        // 错误
                        if (x === 1) {
                          import { bar } from &#39;./foo.js&#39;;
                        } else {
                          import { foo } from &#39;./foo.js&#39;;
                        }

                        在浏览器中使用 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>

                        Un article expliquant la modularité dans es6 en détail

                        • 在浏览器中,引入相同的 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>

                        Un article expliquant la modularité dans es6 en détail

                        模块的默认延迟


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

                        Un article expliquant la modularité dans es6 en détail

                        • 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 : l'objet espace de noms de module est un objet étranger d'espace de noms de module qui fournit un accès basé sur les propriétés d'exécution aux liaisons d'exportation de module. Les objets d'espace de noms de module n'ont pas de constructeur ;

                          Namespace:模块命名空间对象是模块命名空间外来对象,它提供对模块导出绑定的基于运行时属性的访问。模块命名空间对象没有构造函数;
                        • HostDefined:字段保留,以按 host environments 使用,需要将附加信息与模块关联。

                        Module Environment Record

                        • 模块环境记录是一种声明性环境记录,用于表示ECMAScript模块的外部作用域。除了普通的可变和不可变绑定之外,模块环境记录还提供了不可变的 import 绑定,这些绑定提供了对存在于另一个环境记录中的目标绑定的间接访问。

                        不可变绑定就是当前的模块引入其他的模块,引入的变量不能修改,这就是模块独特的不可变绑定。

                        Es Module 的解析流程


                        在开始之前,我们先大概了解一下整个流程大概是怎么样的,先有一个大概的了解:

                        • 阶段一:构建(Construction),根据地址查找 js 文件,通过网络下载,并且解析模块文件为 Module Record;

                        • 阶段二:实例化(Instantiation),对模块进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址;

                        • 阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中;

                        Construction 构建阶段

                        • loader 负责对模块进行寻址及下载。首先我们修改一个入口文件,这在 HTML 中通常是一个 <script type="module"></script> 的标签来表示一个模块文件。

                        Un article expliquant la modularité dans es6 en détail

                        • 模块继续通过 import语句声明,在 import声明语句中有一个 模块声明标识符(ModuleSpecifier),这告诉 loader 怎么查找下一个模块的地址。

                        Un article expliquant la modularité dans es6 en détail

                        • 每一个模块标识号对应一个 模块记录(Module Record),而每一个 模块记录 包含了 JavaScript代码执行上下文ImportEntriesLocalExportEntriesIndirectExportEntriesStarExportEntries。其中 ImportEntries 值是一个 ImportEntry Records 类型,而 LocalExportEntriesIndirectExportEntriesStarExportEntries 是一个 ExportEntry Records 类型。

                        ImportEntry Records

                        • 一个 ImportEntry Records 包含三个字段 ModuleRequestImportNameLocalName;

                        ModuleRequest: 一个模块标识符(ModuleSpecifier);

                        ImportName: 由 ModuleRequest 模块标识符的模块导出所需绑定的名称。值 namespace-object 表示导入请求是针对目标模块的命名空间对象的;

                        LocalName: 用于从导入模块中从当前模块中访问导入值的变量;

                        • 详情可参考下图:Un article expliquant la modularité dans es6 en détail
                        • 下面这张表记录了使用 import 导入的 ImportEntry Records
                        HostDefined : champ réservé à l'utilisation par les environnements hôtes, des informations supplémentaires sont requises pour s'associer au module. Enregistrement d'environnement de module
                        La liaison immuable signifie que le module actuel introduit d'autres modules et que les variables introduites ne peuvent pas être modifiées. Il s'agit de la liaison immuable unique du module.

                        Processus d'analyse du module Es


                        Phase 2 : Instanciation (Instantiation code>), instanciez le module, allouez de l'espace mémoire, analysez les instructions d'importation et d'exportation du module et pointez le module vers l'adresse mémoire correspondante ;<tbody> <tr> <td>Phase 3 : Exécuter (<code>Évaluation code>), exécutez le code, calculez la valeur et remplissez la valeur dans l'adresse mémoire ;Phase de construction de la construction
                        Un article expliquant la modularité dans es6 en détail p>
                          Le module continue d'être déclaré via l'instruction import. Il y a un identifiant de déclaration de module (ModuleSpecifier) dans l'import. Cette instruction indique au loader comment trouver l'adresse du module suivant. Un article expliquant la modularité dans es6 en détail p>
                          Un ImportEntry Records contient trois champs ModuleRequest, <code>ImportName, LocalName;ModuleRequest : un identifiant de module (ModuleSpecifier);ImportName : par ModuleRequest Le nom de la liaison requise pour l'export du module de l'identifiant du module. La valeur namespace-object indique que la demande d'import concerne l'objet espace de noms du module cible ;Veuillez vous référer à l'image ci-dessous pour plus de détails :Un article expliquant la modularité dans es6 en détailLe tableau suivant enregistre les instances du champ ImportEntry Records importé à l'aide de import : Instruction d'importation Formulaire)Identifiant du module (ModuleRequest)Nom de l'importation (ImportName)Nom local (LocalName)
                          L'enregistrement d'environnement de module est un enregistrement d'environnement déclaratif utilisé pour représenter la portée externe d'un module ECMAScript. En plus des liaisons mutables et immuables ordinaires, les enregistrements d'environnement de module fournissent également des liaisons import immuables, qui fournissent une liaison à une cible qui existe dans un autre enregistrement d'environnement avec accès indirect.
                          Avant de commencer, ayons une idée générale de ce à quoi ressemble l'ensemble du processus. Avoir une compréhension générale :
                            Phase 1 : Construction (Construction), recherche selon l'adresse js, téléchargez-le via Internet et analysez le fichier de module dans Module Record;
                            loader est responsable de l'adressage et du téléchargement des modules. Tout d'abord, nous modifions un fichier d'entrée, qui en HTML est généralement une balise <script type="module"></script> pour représenter un fichier de module.
                            Chaque numéro d'identification de module correspond à un Enregistrement de module, et chaque Enregistrement de module contient un Code JavaScript, un Contexte d'exécution , ImportEntries, LocalExportEntries, IndirectExportEntries, StarExportEntries. La valeur ImportEntries est de type ImportEntry Records et LocalExportEntries, IndirectExportEntries, StarExportEntries Est un type ExportEntry Records.

                            ImportEntry Records

                              LocalName : variable utilisée pour accéder à la valeur importée du module courant depuis le module importé ;
                                🎜import React from "react";🎜🎜"react"🎜🎜"default"🎜🎜"React "🎜🎜🎜🎜importer * en tant que Moment depuis "react";🎜🎜"react"🎜🎜namespace-obj🎜🎜"Moment"🎜🎜🎜🎜import {useEffect} depuis "react";🎜🎜"react"🎜🎜" useEffect"🎜🎜"useEffect"🎜🎜🎜🎜import {useEffect as effect } from "react";🎜🎜"react"🎜🎜"useEffect"🎜🎜"effect"🎜🎜🎜🎜

                                ExportEntry Records

                                • Un ExportEntry Records contient quatre champs ExportName, ModuleRequest, ImportName, LocalName
                              • , qui est différent de ImportEntry Records, possède un ExportName supplémentaire.

                              ExportName : Le nom que ce module utilise pour se lier lors de l'exportation. ExportEntry Records 包含四个字段 ExportNameModuleRequestImportNameLocalName,和 ImportEntry Records不同的是多了一个 ExportName

                            ExportName: 此模块用于导出时绑定的名称。

                            • 下面这张表记录了使用 export 导出的 ExportEntry Records

                              Le tableau suivant enregistre des exemples de champs ExportEntry Records exportés à l'aide de export :

                              Nom de l'importation par défaut" nullnull

                              "f"export default function () {}"default"nullnull""export par défaut 42 ; "par défaut"nullnull""export {x};nullnull"x"exporter {v sous x };"x"null"v"exporter {x} depuis "mod";"x""mod""x"nullexporter {v as x} depuis "mod";"x""mod""v"nullexport * depuis "mod";null"mod "tout sauf-par défautnullexport * as ns from "mod";"ns"mod"allnull
                            • Retour au sujet

                            • Ce n'est qu'après avoir analysé l'Enregistrement du module actuel que vous pourrez savoir de quels sous-modules dépend le module actuel, puis vous devrez résoudre le sous-module. module, obtenez le sous-module, puis analysez le sous-module et effectuez continuellement ce processus de résolution -> récupération -> le résultat est comme indiqué dans la figure ci-dessous : Module Record 之后,才能知道当前模块依赖的是那些子模块,然后你需要 resolve 子模块,获取子模块,再解析子模块,不断的循环这个流程 resolving -> fetching -> parsing,结果如下图所示:

                            • Un article expliquant la modularité dans es6 en détail

                              • 这个过程也称为 静态分析,不会运行JavaScript代码,只会识别 exportimport 关键字,所以说不能在非全局作用域下使用 import,动态导入除外。
                              • 如果多个文件同时依赖一个文件呢,这会不会引起死循环,答案是不会的。
                              • loader 使用 Module Map 对全局的 MOdule Record 进行追踪、缓存这样就可以保证模块只被 fetch 一次,每个全局作用域中会有一个独立的 Module Map。

                              MOdule Map 是由一个 URL 记录和一个字符串组成的key/value的映射对象。URL记录是获取模块的请求URL,字符串指示模块的类型(例如。“javascript”)。模块映射的值要么是模块脚本,null(用于表示失败的获取),要么是占位符值“fetching(获取中)”。

                              Un article expliquant la modularité dans es6 en détail

                              linking 链接阶段

                              • 在所有 Module Record 被解析完后,接下来 JS 引擎需要把所有模块进行链接。JS 引擎以入口文件的 Module Record 作为起点,以深度优先的顺序去递归链接模块,为每个 Module Record 创建一个 Module Environment Record,用于管理 Module Record 中的变量。

                              Un article expliquant la modularité dans es6 en détail

                              • Module Environment Record 中有一个 Binding,这个是用来存放 Module Record 导出的变量,如上图所示,在该模块 main.js 处导出了一个 count 的变量,在 Module Environment Record 中的 Binding 就会有一个 count,在这个时候,就相当于 V8 的编译阶段,创建一个模块实例对象,添加相对应的属性和方法,此时值为 undefined 或者 null,为其分配内存空间。
                              • 而在子模块 count.js 中使用了 import 关键字对 main.js 进行导入,而 count.jsimportmain.jsexport 的变量指向的内存位置是一致的,这样就把父子模块之间的关系链接起来了。如下图所示:

                              Un article expliquant la modularité dans es6 en détail

                              • 需要注意的是,我们称 export 导出的为父模块,import 引入的为子模块,父模块可以对变量进行修改,具有读写权限,而子模块只有读权限。

                              Evaluation 求值阶段

                              • 在模块彼此链接完之后,执行对应模块文件中顶层作用域的代码,确定链接阶段中定义变量的值,放入内存中。

                              Es module 是如何解决循环引用的


                              • Es Module 中有5种状态,分别为 unlinkedlinkinglinkedevaluatingevaluated,用循环模块记录(Cyclic Module Records)的 Status 字段来表示,正是通过这个字段来判断模块是否被执行过,每个模块只执行一次。这也是为什么会使用 Module Map 来进行全局缓存 Module Record 的原因了,如果一个模块的状态为 evaluated,那么下次执行则会自动跳过,从而包装一个模块只会执行一次。 Es Module 采用 深度优先

                              🎜🎜
                                🎜Ce processus est également appelé analyse statique, n'exécutera pas de code JavaScript et ne reconnaîtra que les mots-clés export et import, donc import le code ne peut pas être utilisé dans des portées non globales >, sauf pour l'importation dynamique. 🎜🎜Et si plusieurs fichiers dépendent d'un seul fichier en même temps ? Cela provoquera-t-il une boucle infinie ? 🎜🎜<code>loader utilise Module Map pour suivre et mettre en cache l'MOdule Record global afin de garantir que les modules sont uniquement récupérés > Une fois, il y aura une carte de modules indépendante dans chaque portée globale. 🎜
                              🎜MOdule Map est un objet de mappage clé/valeur composé d'un enregistrement URL et d'une chaîne. L'enregistrement URL est l'URL de requête pour obtenir le module, une chaîne indiquant le type de module (par exemple "javascript"). La valeur de la carte de module est soit le script du module, null (utilisé pour indiquer un échec de récupération), soit la valeur d'espace réservé « récupération ». 🎜
                              🎜Un article expliquant la modularité dans es6 en détail🎜🎜phase de liaison de liaison🎜
                                🎜Une fois que tout Enregistrement de module a été analysé, alors le moteur JS doit relier tous les modules. Le moteur JS prend l'Enregistrement de module du fichier d'entrée comme point de départ, relie récursivement les modules dans l'ordre de profondeur et crée un Enregistrement d'environnement de module pour chaque <code>Module. Enregistrement, utilisé pour gérer les variables dans Module Record. 🎜
                              🎜Un article expliquant la modularité dans es6 en détail🎜
                                🎜Module Environment Record a une Binding, qui est utilisée pour stocker les variables exportées par Module Record, comme le montre la figure ci-dessus, une variable de count est exportée dans main.js de ce module, et Binding dans Module Environment Record Il y aura un count À ce stade, cela équivaut à la phase de compilation de V8, créant un objet instance de module et ajoutant les attributs et méthodes correspondants. cette fois est indéfini ou null, allouez-lui de l'espace mémoire. 🎜🎜Le mot-clé import est utilisé dans le sous-module count.js pour importer main.js, et count.js Le les emplacements mémoire pointés par les variables de <code>import de et de main.js export sont les mêmes, séparant ainsi les les relations entre les modules parent et enfant sont liées. Comme le montre l'image ci-dessous : 🎜
                              🎜Un article expliquant la modularité dans es6 en détail 🎜
                                🎜Il convient de noter que nous appelons ce que export exporte est le module parent, et ce que import introduit est un sous-module. Le module parent peut modifier les variables avec des autorisations de lecture et d'écriture, tandis que les sous-modules n'ont que des autorisations de lecture. 🎜
                              🎜Phase d'évaluation🎜
                                🎜Une fois les modules liés les uns aux autres, exécutez le fichier module Le code dans la portée de niveau supérieur détermine la valeur de la variable définie dans la phase de liaison et la met en mémoire. 🎜

                              Comment le module Es résout les références circulaires


                                🎜🎜Il existe 5 types dans le statut du Module Es, respectivement unlinked, linking, linked, evaluating et evaluated, utiliser Il est représenté par le champ Status des enregistrements du module cyclique (Cyclic Module Records). C'est grâce à ce champ qu'on juge si le module a été exécuté une seule fois. . C'est aussi pourquoi Module Map est utilisé pour mettre globalement en cache Module Record. Si l'état d'un module est évalué, alors la prochaine exécution le sera. être automatiquement ignoré, de sorte que l'empaquetage d'un module ne soit exécuté qu'une seule fois. Es Module utilise la méthode deep first pour parcourir le graphe du module. Chaque module n'est exécuté qu'une seule fois, ce qui évite une boucle infinie. 🎜

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

                              Un article expliquant la modularité dans es6 en détail

                              • 看下面的例子,所有的模块只会运行一次:
                              // 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 ,得出以下结果:

                              Un article expliquant la modularité dans es6 en détail

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

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

                              【推荐学习:javascript高级教程

                              Déclaration d'exportation Nom de l'exportation Identifiant du module
                              default
                              default "x"
                              null

          Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

          Déclaration:
          Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer