ホームページ ウェブフロントエンド jsチュートリアル JavaScriptモジュールの循環ロードの実装方法_JavaScriptスキル

JavaScriptモジュールの循環ロードの実装方法_JavaScriptスキル

May 16, 2016 pm 03:26 PM

「循環依存関係」とは、スクリプト a の実行はスクリプト b に依存し、スクリプト b の実行はスクリプト a に依存することを意味します。

// a.js
var b = require('b');

// b.js
var a = require('a');
ログイン後にコピー

通常、「ループ読み込み」は強い結合が存在することを示します。適切に処理しないと、再帰読み込みが発生してプログラムが実行できなくなる可能性があるため、回避する必要があります。

しかし実際には、これを避けるのは難しく、特に複雑な依存関係を持つ大規模なプロジェクトでは、a が b に依存し、b が c に依存し、c が a に依存することが容易になります。これは、モジュール読み込みメカニズムが「ループ読み込み」状況を考慮する必要があることを意味します。

この記事では、JavaScript 言語が「ループ読み込み」を処理する方法を紹介します。現在、最も一般的な 2 つのモジュール形式である CommonJS と ES6 は、処理方法が異なり、異なる結果を返します。

1. CommonJS モジュールの読み込み原理

ES6 が「ループ読み込み」をどのように処理するかを紹介する前に、まず最も一般的な CommonJS モジュール形式の読み込み原理を紹介しましょう。

CommonJSのモジュールはスクリプトファイルです。 require コマンドが初めてスクリプトをロードするとき、スクリプト全体が実行され、メモリ内にオブジェクトが生成されます。

{
 id: '...',
 exports: { ... },
 loaded: true,
 ...
}
ログイン後にコピー

上記のコードでは、オブジェクトの id 属性はモジュール名、exports 属性はモジュールによって出力される各インターフェイス、loaded 属性はモジュールのスクリプトが実行されたかどうかを示すブール値です。他にもたくさんの属性がありますが、ここでは省略します。 (詳細な導入については、「require() ソース コードの解釈」を参照してください。)

今後このモジュールを使用する必要がある場合は、exports 属性から値を取得します。 requireコマンドを再度実行しても、モジュールは再度実行されませんが、値はキャッシュから取得されます。

2. CommonJS モジュールのループ読み込み

CommonJS モジュールの重要な機能は、ロード時の実行です。つまり、すべてのスクリプト コードが必要に応じて実行されます。 CommonJS のアプローチは、モジュールが「ループロード」されると、実行された部分のみが出力され、未実行の部分は出力されないというものです。

公式ドキュメントの例を見てみましょう。スクリプトファイルa.jsのコードは以下のとおりです。

exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
ログイン後にコピー

上記のコードでは、a.js スクリプトは最初に Done 変数を出力し、次に別のスクリプト ファイル b.js を読み込みます。この時点で a.js コードはここで停止し、b.js の実行が完了するのを待ってから実行を続行することに注意してください。

b.js コードをもう一度見てください。

exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
ログイン後にコピー

上記のコードでは、b.jsを2行目まで実行すると、a.jsが読み込まれます。このとき「ループロード」が発生します。システムは、a.js モジュールに対応するオブジェクトの exports 属性の値を取得します。ただし、a.js はまだ実行されていないため、exports 属性から取得できるのは実行された部分だけであり、最終的な値は取得できません。

a.js の実行部分は 1 行だけです。


exports.done = false;


したがって、b.js の場合、a.js から行われた変数を 1 つだけ入力し、値は false になります。

その後、b.js は実行を継続し、すべての実行が完了すると、実行権が a.js に戻ります。したがって、a.js は実行が完了するまで実行を続けます。このプロセスを検証するスクリプト main.js を作成します。

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
ログイン後にコピー

main.jsを実行すると以下のようになります。

$ node main.js

在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true
ログイン後にコピー

上記のコードは 2 つのことを証明しています。まず、b.jsではa.jsは実行されておらず、最初の行だけが実行されています。次に、main.js を 2 行目まで実行すると、再び b.js が実行されるのではなく、キャッシュされた b.js の実行結果、つまり 4 行目が出力されます。

exports.done = true;

3. ES6 モジュールのループロード

ES6 モジュールの動作メカニズムは CommonJS とは異なります。モジュール読み込みコマンドのインポートに遭遇した場合、モジュールは実行されず、参照が生成されるだけです。本当に使用する必要があるまで待ってから、モジュール内の値を取得します。

したがって、ES6 モジュールは動的参照であり、値のキャッシュの問題はなく、モジュール内の変数は、それらが配置されているモジュールにバインドされます。以下の例を参照してください。

// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);
ログイン後にコピー

上記のコードでは、最初にロードされたとき、m1.js の変数 foo は bar と等しくなりますが、500 ミリ秒後には再び baz と等しくなります。

m2.js がこの変更を正しく読み取れるかどうかを確認してみましょう。

$ babel-node m2.js

bar
baz
ログイン後にコピー

上記のコードは、ES6 モジュールが実行結果をキャッシュせず、ロードされたモジュールの値を動的に取得し、変数が常にその変数が配置されているモジュールにバインドされていることを示しています。

これにより、ES6 は CommonJS とは本質的に異なる方法で「ループ読み込み」を処理します。 ES6 は「ループ読み込み」が発生するかどうかをまったく気にせず、ロードされたモジュールへの参照を生成するだけです。開発者は、値が実際に取得されるときにその値が取得できることを確認する必要があります。

次の例を参照してください (Axel Rauschmayer 博士による「Exploring ES6」からの抜粋)。

// a.js
import {bar} from './b.js';
export function foo() {
 bar(); 
 console.log('执行完毕');
}
foo();

// b.js
import {foo} from './a.js';
export function bar() { 
 if (Math.random() > 0.5) {
 foo();
 }
}
ログイン後にコピー

按照CommonJS规范,上面的代码是没法执行的。a先加载b,然后b又加载a,这时a还没有任何执行结果,所以输出结果为null,即对于b.js来说,变量foo的值等于null,后面的foo()就会报错。

但是,ES6可以执行上面的代码。

$ babel-node a.js

执行完毕

a.js之所以能够执行,原因就在于ES6加载的变量,都是动态引用其所在的模块。只要引用是存在的,代码就能执行。

我们再来看ES6模块加载器SystemJS给出的一个例子。

// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
 counter++;
 return n == 0 || odd(n - 1);
}

// odd.js
import { even } from './even';
export function odd(n) {
 return n != 0 && even(n - 1);
}
ログイン後にコピー

上面代码中,even.js里面的函数foo有一个参数n,只要不等于0,就会减去1,传入加载的odd()。odd.js也会做类似操作。

运行上面这段代码,结果如下。

$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17
ログイン後にコピー

上面代码中,参数n从10变为0的过程中,foo()一共会执行6次,所以变量counter等于6。第二次调用even()时,参数n从20变为0,foo()一共会执行11次,加上前面的6次,所以变量counter等于17。

这个例子要是改写成CommonJS,就根本无法执行,会报错。

// even.js
var odd = require('./odd');
var counter = 0;
exports.counter = counter;
exports.even = function(n) {
 counter++;
 return n == 0 || odd(n - 1);
}

// odd.js
var even = require('./even').even;
module.exports = function(n) {
 return n != 0 && even(n - 1);
}
ログイン後にコピー

上面代码中,even.js加载odd.js,而odd.js又去加载even.js,形成"循环加载"。这时,执行引擎就会输出even.js已经执行的部分(不存在任何结果),所以在odd.js之中,变量even等于null,等到后面调用even(n-1)就会报错。

$ node
> var m = require('./even');
> m.even(10)
TypeError: even is not a function
ログイン後にコピー

[说明] 本文是我写的《ECMAScript 6入门》第20章《Module》中的一节。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

独自のJavaScriptライブラリを作成および公開するにはどうすればよいですか? 独自のJavaScriptライブラリを作成および公開するにはどうすればよいですか? Mar 18, 2025 pm 03:12 PM

記事では、JavaScriptライブラリの作成、公開、および維持について説明し、計画、開発、テスト、ドキュメント、およびプロモーション戦略に焦点を当てています。

ブラウザでのパフォーマンスのためにJavaScriptコードを最適化するにはどうすればよいですか? ブラウザでのパフォーマンスのためにJavaScriptコードを最適化するにはどうすればよいですか? Mar 18, 2025 pm 03:14 PM

この記事では、ブラウザでJavaScriptのパフォーマンスを最適化するための戦略について説明し、実行時間の短縮、ページの負荷速度への影響を最小限に抑えることに焦点を当てています。

フロントエンドのサーマルペーパーレシートのために文字化けしたコード印刷に遭遇した場合はどうすればよいですか? フロントエンドのサーマルペーパーレシートのために文字化けしたコード印刷に遭遇した場合はどうすればよいですか? Apr 04, 2025 pm 02:42 PM

フロントエンドのサーマルペーパーチケット印刷のためのよくある質問とソリューションフロントエンド開発におけるチケット印刷は、一般的な要件です。しかし、多くの開発者が実装しています...

ブラウザ開発者ツールを使用してJavaScriptコードを効果的にデバッグするにはどうすればよいですか? ブラウザ開発者ツールを使用してJavaScriptコードを効果的にデバッグするにはどうすればよいですか? Mar 18, 2025 pm 03:16 PM

この記事では、ブラウザ開発者ツールを使用した効果的なJavaScriptデバッグについて説明し、ブレークポイントの設定、コンソールの使用、パフォーマンスの分析に焦点を当てています。

誰がより多くのPythonまたはJavaScriptを支払われますか? 誰がより多くのPythonまたはJavaScriptを支払われますか? Apr 04, 2025 am 12:09 AM

スキルや業界のニーズに応じて、PythonおよびJavaScript開発者には絶対的な給与はありません。 1. Pythonは、データサイエンスと機械学習でさらに支払われる場合があります。 2。JavaScriptは、フロントエンドとフルスタックの開発に大きな需要があり、その給与もかなりです。 3。影響要因には、経験、地理的位置、会社の規模、特定のスキルが含まれます。

ソースマップを使用して、マイナイドJavaScriptコードをデバッグするにはどうすればよいですか? ソースマップを使用して、マイナイドJavaScriptコードをデバッグするにはどうすればよいですか? Mar 18, 2025 pm 03:17 PM

この記事では、ソースマップを使用して、元のコードにマッピングすることにより、Minified JavaScriptをデバッグする方法について説明します。ソースマップの有効化、ブレークポイントの設定、Chrome DevtoolsやWebpackなどのツールの使用について説明します。

JavaScriptを使用して、同じIDを持つArray要素を1つのオブジェクトにマージする方法は? JavaScriptを使用して、同じIDを持つArray要素を1つのオブジェクトにマージする方法は? Apr 04, 2025 pm 05:09 PM

同じIDを持つ配列要素をJavaScriptの1つのオブジェクトにマージする方法は?データを処理するとき、私たちはしばしば同じIDを持つ必要性に遭遇します...

Console.log出力の違い結果:なぜ2つの呼び出しが異なるのですか? Console.log出力の違い結果:なぜ2つの呼び出しが異なるのですか? Apr 04, 2025 pm 05:12 PM

Console.log出力の違いの根本原因に関する詳細な議論。この記事では、Console.log関数の出力結果の違いをコードの一部で分析し、その背後にある理由を説明します。 �...

See all articles