ホームページ  >  記事  >  ウェブフロントエンド  >  es6 のモジュール化を 1 つの記事で完全に理解する

es6 のモジュール化を 1 つの記事で完全に理解する

藏色散人
藏色散人転載
2023-02-17 11:17:592464ブラウズ

前のレビュー

  • 前回の記事では CommonJs について説明しました。まだ読んでいない場合は、この記事が掲載されている列を見つけることができます。学ぶために位置しています。
  • CommonJs には多くの優れた機能があります。以下で簡単に説明します:
  • モジュール コードはロード後にのみ実行されます。

  • モジュールは 1 回だけロードできます;

  • モジュールは他のモジュールのロードを要求できます;

  • サポートされています循環依存関係;

  • モジュールはパブリック インターフェイスを定義でき、他のモジュールはこのパブリック インターフェイスに基づいて観察および対話できます;

天下狠CommonJs 久矣

  • Es Module は、ブラウザを通じて、またはサードパーティのローダーやビルド ツールを使用してネイティブにロードできるという点で独特です。
  • Es module モジュールをサポートするブラウザは、トップレベル モジュールから依存関係グラフ全体をロードでき、それは非同期で行われます。ブラウザはエントリ モジュールを解析し、依存関係を特定し、依存モジュールに対するリクエストを送信します。これらのファイルがネットワーク経由で返された後、ブラウザーはその依存関係を解決します。これらの二次的な依存関係が読み込まれていない場合は、さらに多くのリクエストが送信されます。
  • この非同期再帰読み込みプロセスは、アプリケーション全体の依存関係グラフが解決されるまで続行されます。依存関係グラフが解析された後、参照プログラムはモジュールを正式にロードできます。
  • Es Module は、CommonJsAMD の多くの優れた機能を借用するだけでなく、いくつかの新しい動作も追加します。
  • Es Module

    はデフォルトで厳密モードで実行されます;

  • Es Module

    はグローバルを共有しません名前空間;

  • Es Module

    最上位の this の値は 未定義 (通常のスクリプトは window );

  • モジュール内の
  • var

    宣言は window オブジェクトに追加されません;

  • ##Es Module
  • は非同期でロードおよび実行されます;

    エクスポートとインポート

モジュール関数は主に次のように構成されます

exports
  • import の 2 つのコマンドのうちの 1 つです。 export
  • コマンドはモジュールの外部インターフェイスを指定するために使用され、
  • import コマンドは他のモジュールによって提供される関数をインポートするために使用されます。 エクスポートの基本的な使い方

エクスポートの基本形式:

    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 };
          デフォルトのエクスポート。モジュールが持つことができるデフォルトのエクスポートは 1 つだけであることに注意してください:
        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 = 1export moment// function和class的输出,也必须遵守这样的写法function foo(x, y) {    return x+y
          }export foo复制代码
          import の基本的な使用法

        export

        コマンドを使用してモジュールの外部インターフェイスを定義した後、他の js ファイルを
          import
        • コマンド モジュール全体
          import {foo,age,nickname} from '模块标识符'
          モジュール識別子は、現在のモジュールへの相対パス、絶対パス、または純粋な文字列にすることができますが、そうではありません。文字列などによる動的計算の結果。
          import
        • コマンドは、他のモジュールからインポートされる変数名を指定する中括弧を受け入れます。変数名は、インポートされたモジュールの外部インターフェイスの名前と同じである必要があります。モジュール。
        • インポートされた変数は読み取り専用インターフェイスであるため、再割り当てできません。オブジェクトの場合は、オブジェクトのプロパティを再割り当てできます。エクスポートされたモジュールは値を変更でき、インポートされた変数もそれに応じて変更されます。
          • 从上图可以看得出来,对象的属性被重新赋值了,而变量的则报了 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
        import("./foo.js").then((module) => {  const { default: foo, bar, baz } = module;  console.log(foo); // [Function: foo]
          console.log(bar); // 777
          console.log(baz); // moment});复制代码

        使用顶层 await

        • 在经典脚本中使用 await 必须在带有 async 的异步函数中使用,否则会报错:
        import("./foo.js").then((module) => {
          const { default: foo, bar, baz } = module;
          console.log(foo); // [Function: foo]
          console.log(bar); // 777
          console.log(baz); // moment
        });
        • 而在模块中,你可以直接使用 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></script><script></script>
        • 模块默认情况下是延迟的,因此你还可以使用 defer 的方式延迟你的 nomodule 脚本:
          <script>      
          console.log("模块情况下的");
            </script>    
            <script></script>
            <script>
              console.log("正常 script标签");    
              </script>

        es6 のモジュール化を 1 つの記事で完全に理解する

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

        es6 のモジュール化を 1 つの記事で完全に理解する

        模块的默认延迟

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

        es6 のモジュール化を 1 つの記事で完全に理解する

        • deferasync はオプションの属性です。nomodule スクリプト defer# # では、どちらか 1 つだけを選択できます。 #HTML が解析されるまで現在のスクリプトは解析されません。また、asyncHTML と並行して解析され、 の解析をブロックしません。 HTML では、モジュール スクリプトで async 属性を指定できますが、モジュールはデフォルトで遅延するため、 defer では無効です。
        • モジュール スクリプトの場合、
        • async 属性が存在する場合、モジュール スクリプトとそのすべての依存関係が並行して解析およびフェッチされ、モジュール スクリプトはすぐに実行されます。利用可能。
        Es モジュールと Commonjs の違い

        • Es モジュール モジュールについて説明する前に、まず Es モジュール## について理解する必要があります。 # と Commonjs はまったく異なり、次の 3 つのまったく異なるタイプがあります。
          CommonJS
        1. モジュールは値 ## のコピーを出力します。 #Es Module 出力は値への参照です。CommonJS
        2. モジュールは実行時にロードされ、
        3. Es Module はコンパイル時の出力インターフェイスです。 。 CommonJS
        4. モジュールの
        5. require() はモジュールを同期的にロードし、ES6 モジュールの import コマンドはモジュールをロードします。非同期的に実行され、独立したモジュール依存関係分析ステージがあります。
        6. 2 番目の違いは、
        CommonJS
          がオブジェクト (つまり、
        • module.exports プロパティ) をロードするためです。このオブジェクトは、スクリプトが実行中です。完了後に生成されます。 Es Module はオブジェクトではなく、その外部インターフェイスは単なる静的な定義であり、コードの静的解析フェーズ中に生成されます。 Commonjs
        • 出力されるのは値のコピーです。つまり、一度値が出力されると、モジュール内で変更を加えても値には影響しません。詳しくは前回の記事をご覧ください。
        • Es Module
        • の動作機構は
        • CommonJS とは異なります。 JS エンジン スクリプトを静的に分析する場合、モジュール読み込みコマンド import が検出されると、読み取り専用の参照が生成されます。スクリプトが実際に実行されると、この読み取り専用参照に基づいて、ロードされたモジュールから値が取得されます。つまり、import は接続パイプであり、元の値が変更されると、import で読み込まれる値もそれに応じて変更されます。したがって、Es Module は動的参照であり、値をキャッシュしません。モジュール内の変数は、それらが配置されているモジュールにバインドされます。 Es モジュールの動作原理の関連概念

        動作原理を学ぶ前に、関連する概念を理解しておくとよいでしょう。

        • モジュール レコード

        モジュール レコード (

        モジュール レコード
          ) は、単一モジュール (現在のモジュール) のインポートとエクスポートに関する構造情報をカプセル化します。この情報は、接続されたモジュール セットのインポートとエクスポートをリンクするために使用されます。モジュール レコードは 4 つのフィールドで構成され、モジュールの実行時にのみ使用されます。 4 つのフィールドは次のとおりです:
        Realm
          : 現在のモジュールのスコープを作成します;
        1. Environment
        2. : モジュールの先頭ランタイム プロパティベースのアクセスの -level バインディング環境レコード。モジュール名前空間オブジェクトにはコンストラクターがありません。
        3. HostDefined
        4. : フィールドは
        5. ホスト環境 による使用のために予約されており、モジュールに関連付けるには追加情報が必要です。
        6. モジュール環境レコードモジュール環境レコードは、ECMAScript モジュールの外部スコープを表すために使用される宣言型環境レコードです。通常の可変および不変バインディングに加えて、モジュール環境レコードは、別の環境レコードの間接アクセスに存在するターゲットへのバインディングを提供する不変 import
        7. バインディングも提供します。

          不変バインディングとは、現在のモジュールが他のモジュールを導入し、導入された変数は変更できないことを意味します。これは、モジュール固有の不変バインディングです。
        • Es モジュールの解析プロセス

          • 始める前に、プロセス全体がどのようなものであるかを大まかに把握して、一般的な理解を深めましょう:
          1. フェーズ 1: 構築 (Construction)、アドレスに従って js ファイルを見つけ、ネットワーク経由でダウンロードし、モジュール ファイルを に解析します。モジュール レコード ;
          2. フェーズ 2: インスタンス化 (Instantiation)、モジュールをインスタンス化し、メモリ領域を割り当て、モジュールのインポートおよびエクスポート ステートメントを解析し、モジュールを対応するメモリ アドレス;
          3. フェーズ 3: 実行 (評価)、コードを実行し、値を計算し、その値をメモリ アドレスに入力します;

          建設建設フェーズ

          • loader は、モジュールのアドレス指定とダウンロードを担当します。まずエントリ ファイルを変更します。これは通常、HTML ではモジュール ファイルを表す <script type="module"></script> タグです。

          es6 のモジュール化を 1 つの記事で完全に理解する

          • モジュールは、import ステートメントを通じて宣言され続けます。import にはモジュール宣言識別子があります。 宣言ステートメント。文字 (ModuleSpecifier)。loader に次のモジュールのアドレスを見つける方法を指示します。

          es6 のモジュール化を 1 つの記事で完全に理解する

          • 各モジュール識別番号は、 モジュール レコード (モジュール レコード)、および各 モジュール レコード# #に対応します。 #JavaScript コード実行コンテキストImportEntriesLocalExportEntriesIndirectExportEntriesStarExportEntries ##が含まれます#。 ImportEntries 値は ImportEntry Records タイプであり、LocalExportEntriesIndirectExportEntriesStarExportEntries です。 ExportEntry Records タイプ。
          • ImportEntry Records

          A
            ImportEntry Records
          • には 3 つのフィールド ModuleRequestImportName、# # が含まれています#LocalName;
          • ModuleRequest: モジュール識別子 (
          ModuleSpecifier
            );
          1. ImportName: ModuleRequest
          2. によって生成されました。モジュール識別子のモジュールエクスポートに必要なバインディングの名前。値
          3. namespace-object は、インポート要求がターゲット モジュールの名前空間オブジェクトに対するものであることを示します。LocalName: インポートされたモジュールから現在のモジュールのインポート値にアクセスするために使用される変数。
          4. 詳細については、以下の図を参照してください。
          • 次の表には、es6 のモジュール化を 1 つの記事で完全に理解する を使用してインポートされた
          • ImportEntry Records
          • フィールドが記録されています。 import 例:
          Import ステートメント Fromモジュール識別子(ModuleRequest)インポート名(ImportName) LocalName##「react」から React をインポート;#「react」「default」"React"import * as Moment from "react";"react"namespace -obj"モーメント""反応" から {useEffect} をインポート;"反応""useEffect" "useEffect""useEffect"
          ##import {useEffect をエフェクトとして } from "react"; "react"
          "効果"############

          ExportEntry Records

          • A ExportEntry Records には、4 つのフィールド ExportNameModuleRequestImportName が含まれます。 LocalNameImportEntry Records は、追加の ExportName があるという点で異なります。
          1. ExportName: このモジュールがエクスポート時にバインドするために使用する名前。
          • #次の表には、

            export:# を使用してエクスポートされた

            ExportEntry Records フィールドの例が記録されています。 ##エクスポート宣言#エクスポート変数 v;"v"nullnull"v"デフォルト関数 f() をエクスポート {}"default"nullnull"f"export デフォルト関数 () {}"default"nullnull""デフォルト 42 をエクスポート;"デフォルト"nullnull"""x"#export {v as x};"x"#null##「mod」から {x} をエクスポート;"x""mod""x "null「mod」から {v を x} にエクスポートします;"x""mod""v"nullデフォルト以外のすべて#nullnull
            エクスポート名 モジュール識別子 インポート名 ローカル名
            default
            default ##エクスポート {x};
            null null "x"
            null "v"
            ##エクスポート * "mod" から; null "mod"
            ##export * as ns from "mod"; "ns "mod" all
          • トピックに戻る

          • 現在の モジュール レコード を解析した後でのみ、現在のモジュールがどのサブモジュールに依存しているかを知ることができます。サブモジュールを 解決し、サブモジュールを取得し、次にサブモジュールを解析し、このプロセスの解決 -> フェッチ -> 解析を継続的に繰り返す必要があります。結果は次の図に示すようになります:

          • es6 のモジュール化を 1 つの記事で完全に理解する

            • このプロセスは、静的分析とも呼ばれます。JavaScript コードは実行されず、export## のみが識別されます。 # および import キーワードがあるため、動的インポートを除き、import は非グローバル スコープでは使用できません。
            • 複数のファイルが 1 つのファイルに同時に依存している場合はどうなりますか?これにより無限ループが発生しますか?答えはノーです。
            • loader Module Map を使用してグローバル MOdule Record を追跡およびキャッシュし、モジュールが fetch## のみであることを確認します #一旦、各グローバル スコープに独立したモジュール マップが存在します。
            MOdule Map は、URL レコードと文字列で構成されるキー/値マッピング オブジェクトです。 URL レコードは、モジュールを取得するためのリクエスト URL、モジュールのタイプを示す文字列 (例: "javascript") です。モジュール マップの値は、モジュール スクリプト、null (フェッチの失敗を示すために使用)、またはプレースホルダー値「fetching」のいずれかです。

            es6 のモジュール化を 1 つの記事で完全に理解するlinking リンクフェーズ

            すべての
              Module Record
            • が解析された後、次の JS エンジンは Link all を必要とします。モジュール。 JS エンジンは、エントリ ファイルの モジュール レコード を開始点として使用し、モジュールを深さ優先順に再帰的にリンクし、各 モジュールに対して モジュール環境レコード を作成します。 RecordModule Record 内の変数を管理するために使用されます。

            es6 のモジュール化を 1 つの記事で完全に理解する

            モジュール環境レコード
              には、
            • Module 変数を保存するために使用される Binding があります。上の図に示すように、Record によってエクスポートされ、モジュール環境レコード のモジュール main.js にある count の変数をエクスポートします。 ##Binding には count が含まれます。この時点では、モジュール インスタンス オブジェクトを作成し、対応する属性とメソッドを追加する V8 のコンパイル フェーズに相当します。このときの値は unknown または null なので、メモリ領域を割り当てます。 import キーワードは、サブモジュール
            • count.js
            • main.jscount をインポートするために使用されます。 js の #importmain.jsexport 変数は同じメモリの場所を指しているため、親モジュールと子モジュール間の関係がリンクされています。 。以下に示すように:
              • 需要注意的是,我们称 export 导出的为父模块,import 引入的为子模块,父模块可以对变量进行修改,具有读写权限,而子模块只有读权限。

              Evaluation 求值阶段

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

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

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

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

              es6 のモジュール化を 1 つの記事で完全に理解する

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

              es6 のモジュール化を 1 つの記事で完全に理解する

      • 以上がes6 のモジュール化を 1 つの記事で完全に理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

        声明:
        この記事はjuejin.imで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。